Срез 0: скелет tabular-parser — без фронта и БД, воркер NATS без JetStream, парсер-заглушка, контракт v1.0 #3

Merged
aep merged 50 commits from feat/1-slice-0-skeleton into main 2026-10-04 18:41:02 +03:00
Owner

Issue #1 — реализация change slice-0-skeleton (50 коммитов после merge proposal #2). Не закрывает issue: закрытие — после openspec archive командой «закрывай».

Что сделано

  • Вырезано из шаблона: web/, Alembic, db/, models/, repositories/, SMB, raisa_*, лишние зависимости; шаблонный README.md заменён описанием сервиса; CLAUDE.md актуализирован.
  • Инструменты: api/pyproject.toml (ruff, mypy), Makefile (venv, lint, test, test-integration, check, check-full, up), Dockerfile (python:3.11-slim) и .dockerignore, api/.env.example / api/.env.test, ENV_FILE.
  • Контракт v1.0: копии трёх схем и примеров в api/contracts/, pydantic-модели src/contract/, тест идентичности с ../docs/contracts (байт в байт).
  • Воркер на core NATS без JetStream: подписка queue group tabular-parser; ack processing до чтения S3; process-local LRU job_id против повторных запросов ядра; результат results/{file_id}/{job_id}/result.json и конверт с повторами публикации; транспортные ошибки всегда становятся результатом (SCHEMA_MISMATCH, FILE_READ_ERROR, UNKNOWN_ERROR); корректная остановка; /healthcheck с полем nats.
  • Парсер — заменяемый интерфейс: Parser, NullParser (NOT_IMPLEMENTED), каталог api/rules/.

Проверки

make check-full на HEAD, exit 0: ruff, format, mypy чисто; 265 unit + 8 integration passed (реальные NATS и MinIO из compose docs/dev); docker build ок (образ 268 КБ в /app/api, без .env/.venv). Живой smoke: запрос → ack:<job>:1 (processing) → result:<job>:1 (error NOT_IMPLEMENTED); /healthcheck 200 nats=ok, в том числе с минимальным .env. openspec validate --strict — valid.

Rulings I made

Полный список — openspec/changes/slice-0-skeleton/design.md, таблица «Decisions made without discussion» (№8–47). Ключевые: R-13 Parser.parse синхронный, вызывается через asyncio.to_thread; R-15 фоновый supervisor подключения NATS вместо несуществующего retry_on_failed_connect; R-16 PUBLISH_RETRIES=3 — три повтора после первой попытки; R-17 /healthcheck.nats = ok | down | off; R-18 нечитаемый запрос без job_id логируется и отбрасывается без ответа; R-20 при schema_version 2.0 SCHEMA_MISMATCH идёт без ack; R-21 дедуп по job_id (LRU); R-34 добавлен .dockerignore (образ содержал .env и .venv); R-35 SERVICE_NAME — Literal.

Deferred (в follow-up issue при закрытии)

M-6 (cert_check=False для MinIO), M-9 (/healthcheck ok при незавершённом on_connected, закрытие клиента терминально), M-10 (сырой текст исключений в error.message, Срез 2), O-5 (модели контракта продублированы в arm-core), O-8 (enqueue=True в шаблонном logger.py), stop() при отмене вызывающего, to_thread не отменяется при STOP_TIMEOUT; для Среза 2 — неограниченная очередь, файл целиком в памяти, reply_deadline не проверяется.

��то нужно решить человеку

  • I-4: сценарий «NATS перезапущен» (подписка восстановлена за 20 с) не выполнялся: рестарт общего compose не делали. Проверить вручную или принять риск.
  • Предложения по контракту/OQ (P-1 нечитаемый запрос без ответа; P-2 ack при schema_version 2.0 — spec docs говорит «с ack»; P-3 ключ дедупа job_id или (job_id, attempt); P-4 порт сервиса в docs README) — openspec/changes/slice-0-skeleton/open-questions-proposal.md.

Связанные

Тест идентичности контракта читает ../docs — PR RSHB/arm-ka-docs (контракт v1.0, compose) должен влиться раньше или одновременно. RSHB/arm-core PR #3; RSHB/arm-scan-gateway — в работе.

🤖 Generated with Claude Code

https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2

Issue #1 — реализация change `slice-0-skeleton` (50 коммитов после merge proposal #2). Не закрывает issue: закрытие — после `openspec archive` командой «закрывай». ## Что сделано - **Вырезано из шаблона:** `web/`, Alembic, `db/`, `models/`, `repositories/`, SMB, `raisa_*`, лишние зависимости; шаблонный `README.md` заменён описанием сервиса; `CLAUDE.md` актуализирован. - **Инструменты:** `api/pyproject.toml` (ruff, mypy), `Makefile` (`venv`, `lint`, `test`, `test-integration`, `check`, `check-full`, `up`), `Dockerfile` (`python:3.11-slim`) и `.dockerignore`, `api/.env.example` / `api/.env.test`, `ENV_FILE`. - **Контракт v1.0:** копии трёх схем и примеров в `api/contracts/`, pydantic-модели `src/contract/`, тест идентичности с `../docs/contracts` (байт в байт). - **Воркер на core NATS без JetStream:** подписка queue group `tabular-parser`; ack `processing` до чтения S3; process-local LRU `job_id` против повторных запросов ядра; результат `results/{file_id}/{job_id}/result.json` и конверт с повторами публикации; транспортные ошибки всегда становятся результатом (`SCHEMA_MISMATCH`, `FILE_READ_ERROR`, `UNKNOWN_ERROR`); корректная остановка; `/healthcheck` с полем `nats`. - **Парсер — заменяемый интерфейс:** `Parser`, `NullParser` (`NOT_IMPLEMENTED`), каталог `api/rules/`. ## Проверки `make check-full` на HEAD, exit 0: ruff, format, mypy чисто; 265 unit + 8 integration passed (реальные NATS и MinIO из compose `docs/dev`); `docker build` ок (образ 268 КБ в `/app/api`, без `.env`/`.venv`). Живой smoke: запрос → `ack:<job>:1` (processing) → `result:<job>:1` (error NOT_IMPLEMENTED); `/healthcheck` 200 `nats=ok`, в том числе с минимальным `.env`. `openspec validate --strict` — valid. ## Rulings I made Полный список — `openspec/changes/slice-0-skeleton/design.md`, таблица «Decisions made without discussion» (№8–47). Ключевые: R-13 `Parser.parse` синхронный, вызывается через `asyncio.to_thread`; R-15 фоновый supervisor подключения NATS вместо несуществующего `retry_on_failed_connect`; R-16 `PUBLISH_RETRIES=3` — три повтора после первой попытки; R-17 `/healthcheck.nats` = `ok | down | off`; R-18 нечитаемый запрос без `job_id` логируется и отбрасывается без ответа; R-20 при `schema_version` 2.0 `SCHEMA_MISMATCH` идёт без ack; R-21 дедуп по `job_id` (LRU); R-34 добавлен `.dockerignore` (образ содержал `.env` и `.venv`); R-35 `SERVICE_NAME` — `Literal`. ## Deferred (в follow-up issue при закрытии) M-6 (`cert_check=False` для MinIO), M-9 (`/healthcheck` ok при незавершённом `on_connected`, закрытие клиента терминально), M-10 (сырой текст исключений в `error.message`, Срез 2), O-5 (модели контракта продублированы в arm-core), O-8 (`enqueue=True` в шаблонном `logger.py`), `stop()` при отмене вызывающего, `to_thread` не отменяется при `STOP_TIMEOUT`; для Среза 2 — неограниченная очередь, файл целиком в памяти, `reply_deadline` не проверяется. ## ��то нужно решить человеку - **I-4:** сценарий «NATS перезапущен» (подписка восстановлена за 20 с) не выполнялся: рестарт общего compose не делали. Проверить вручную или принять риск. - Предложения по контракту/OQ (P-1 нечитаемый запрос без ответа; P-2 ack при `schema_version` 2.0 — spec docs говорит «с ack»; P-3 ключ дедупа `job_id` или `(job_id, attempt)`; P-4 порт сервиса в docs README) — `openspec/changes/slice-0-skeleton/open-questions-proposal.md`. ## Связанные Тест идентичности контракта читает `../docs` — PR `RSHB/arm-ka-docs` (контракт v1.0, compose) должен влиться раньше или одновременно. `RSHB/arm-core` PR #3; `RSHB/arm-scan-gateway` — в работе. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
aep added 50 commits 2026-10-04 02:53:20 +03:00
docs/superpowers/plans/2026-10-03-slice-0-skeleton*.md сгенерирован 1:1 из openspec/changes/slice-0-skeleton/tasks.md
(заголовки «Task N» для скриптов subagent-driven-development, Global Constraints из спеки и CLAUDE.md).
.gitignore: .codegraph/, .superpowers/, .bounded, .DS_Store.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Удалены web/, setup/.npmrc, api/alembic/, api/alembic.ini, api/src/db/,
api/src/models/, api/src/repositories/; из setup/setup.sh убраны npm-шаги,
из .gitignore убрана строка Dockerfile. Задача 1.1 отмечена выполненной.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Убраны SPA, CORS, SMB, РАИСА, SQLAlchemy-обработчики и зависимости БД/SMB;
настройки: ENV_FILE, SERVICE_NAME, NATS_ENABLED, WORKER_CONCURRENCY,
PUBLISH_RETRIES, DONE_CACHE_SIZE, MINIO_SECURE.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
max_reconnect_attempts=-1 (D-6), колбэки disconnected/reconnected/closed,
options типизирован как dict[str, Any].

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Нужно для зелёного make lint (R-8).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Задача 2.1. make test допускает код 5 pytest (тестов пока нет). README: все цели Makefile создаются в 2.1 (T3.1).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Скопировано из ../docs/contracts байт в байт (cp, diff -r чистый).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Запрос, конверт (processing|done|needs_check|error), файл результата, ОСВ и формы,
деньги Decimal <-> строка, subject'ы и Nats-Msg-Id. Тесты: идентичность с docs
(skip без docs, ARM_KA_DOCS), примеры, round-trip, согласованность моделей и схем.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Заглушка Среза 0 не читает входные байты; отметки Task 7 / 4.1.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Находки ревью Task 8: T8.2–T8.6.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
on_request: валидация (pydantic + MAJOR = 1) → повторный job_id получает только ack → ack processing → очередь;
рабочие корутины: S3 get → Parser через asyncio.to_thread → файл результата → конверт с metrics.duration_ms →
публикация с Nats-Msg-Id result:<job_id>:<attempt>. SCHEMA_MISMATCH без ack, FILE_READ_ERROR, UNKNOWN_ERROR,
NatsPublishError — error-лог. Docstring протокола Parser: ожидаемые сбои — исход error (T7.1).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Подписка — через on_connected NatsConnection; остановка: воркер (дорабатывает очередь) → NATS → S3. Клиент S3
остаётся и при неудачной проверке бакета, отсутствие бакета — warning (T2.1). Тест lifespan и healthcheck с фейками
(T8.7); формулировки nats в README, .env.example и spec приведены к R-17.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
I9.1: после ack повторная проверка приёма — warning error_category=shutdown, job_id снимается из «в работе».
T9.7: невалидное тело с job_id в работе/обработанным — warning и сброс (msg-id настоящего результата не занимается).
T9.5: error.message исхода парсера — одной строкой, ≤ 1000 символов. T9.6: docstring stop() про to_thread.
T9.1: тест конкуренции без опроса со sleep.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
T9.2: события старта worker.start → nats.start; _connect_s3 — info/warning/error по ответу проверки бакета, клиент
остаётся. T9.3: spec без «R-17». T9.4: README и CLAUDE.md описывают уже существующие файлы без отсылок к будущим задачам.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Приложение поднимается в процессе теста через lifespan с NATS_ENABLED=True; тест в роли ядра кладёт исходник в
uploads/, публикует запрос и собирает recognition.result по своему job_id: ack processing, затем error
NOT_IMPLEMENTED с правильными Nats-Msg-Id и валидные по схеме конверты; повтор запроса — только ещё один ack
(второго результата нет в окне тишины QUIET_WINDOW). Плюс сценарии spec: нет объекта в S3 -> FILE_READ_ERROR и
сервис работает дальше; schema_version 2.0 -> один SCHEMA_MISMATCH без ack.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Два подряд прогона: ruff/mypy чисто, unit 247 passed, integration 8 passed,
образ tabular-parser собран.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Строка .dockerignore убрана из .gitignore (R-34 отменяет R-11). Образ
собирается `make check-full` из рабочего дерева: раньше в /app/api были
.env с паролем MinIO и .venv (175 МБ), теперь только код, контракты,
правила и requirements (264 КБ).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Требование «Ошибки транспорта превращаются в результат» приведено к коду:
SCHEMA_MISMATCH публикуется, если читаемы job_id, file_id и attempt и job_id
ещё не известен; иначе — лог (error/warning) без ack и результата. Добавлены
два сценария (их покрывают test_unreadable_request_is_logged_and_dropped и
test_malformed_request_for_{job_in_progress,done_job}_is_dropped).
В proposal.md поле healthcheck nats — ok/down/off.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
nats-py 2.14 при Permissions Violation не закрывает соединение и не роняет
subscribe/flush/publish, а только вызывает error_cb. Раньше это логировалось
как «Нет связи с NATS, повторяем подключение» с лимитом раз в минуту. Теперь
_on_error распознаёт «permissions violation» и пишет error
error_category=nats_permission с subject, каждый раз и без серии ошибок
подключения. Исправлены ложные утверждения docstring («ошибки прав
проявляются сразу»).

connect получает drain_timeout=DRAIN_TIMEOUT (10 с вместо 30 с nats-py):
STOP_TIMEOUT воркера + drain укладываются в 30 с платформы.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- SERVICE_NAME: Literal["tabular-parser"] (R-35); в config.py, .env.example,
  README и CLAUDE.md исправлено «имя queue group» — queue group задаёт
  константа REQUEST_QUEUE_GROUP в core/nats.py.
- PUBLISH_RETRIES: NonNegativeInt — 0 означает одну попытку без повторов,
  как и принимает NatsConnection.
- Удалён неиспользуемый модульный settings = get_settings() из config.py.
- pyproject.toml: убран override mypy для raisa_fastapi_protected_api
  (зависимость удалена D-1).
- tests/conftest.py: фабрика minimal_settings (Settings из временного .env с
  минимальным окружением, окружение процесса скрыто); test_config.py —
  умолчания при минимальном окружении (M-8), PUBLISH_RETRIES=0/-1,
  SERVICE_NAME≠tabular-parser.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- test_app: приложение с Settings только из MinIO, адреса NATS и DEBUG
  стартует, /healthcheck отвечает 200 с uptime, version, nats.
- test_layout: в requirements.txt нет asyncpg, SQLAlchemy, alembic,
  smbprotocol, argon2-cffi, raisa_fastapi_protected_api; нет каталогов web/,
  api/alembic/, api/src/{db,models,repositories}/.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
aep merged commit e966ce46bb into main 2026-10-04 18:41:02 +03:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: RSHB/arm-tabular-parser#3
No description provided.