Срез 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
2 changed files with 24 additions and 4 deletions
Showing only changes of commit c9cdf7592d - Show all commits

View File

@ -33,9 +33,11 @@
- **Парсер как интерфейс**: `src/parser/` — протокол `Parser` и `NullParser`, возвращающий `status = error`,
`error.code = NOT_IMPLEMENTED` для любого файла (Срез 2 заменяет реализацией §1/§12); `api/rules/README.md` —
место словарей синонимов.
- **Ошибки транспорта** всегда превращаются в результат: запрос не по схеме → `SCHEMA_MISMATCH`; файл не читается из
- **Ошибки транспорта** превращаются в результат: запрос не по схеме → `SCHEMA_MISMATCH`
(если читаемы `job_id`, `file_id`, `attempt`, иначе — лог ошибки без ответа); файл не читается из
S3 → `FILE_READ_ERROR`; неожиданное исключение → `UNKNOWN_ERROR`. Ядро не ждёт таймаута, если сервис жив.
- **Healthcheck** `/healthcheck` шаблона дополняется полем `nats` (`ok`/`down`).
- **Healthcheck** `/healthcheck` шаблона дополняется полем `nats` (`ok`/`down`; `off` — NATS выключен флагом
`NATS_ENABLED=false`).
- **Проверки качества** вместо CI: `make check` (ruff, mypy, pytest без Docker, идентичность контракта) и
`make check-full` (+ интеграционный тест против compose, `docker build`) — по `../docs/dev/checks.md`.

View File

@ -49,8 +49,15 @@ Stateless-сервис разбора табличных файлов (XLS/XLSX/
### Requirement: Ошибки транспорта превращаются в результат
Сервис SHALL отвечать конвертом `status = error` в случаях: тело не проходит схему или `MAJOR` версии ≠ 1 →
`SCHEMA_MISMATCH` (если `job_id` читаем); объект S3 не читается → `FILE_READ_ERROR`; исключение парсера →
`UNKNOWN_ERROR` (сообщение без трассировки). Сервис MUST NOT падать и MUST NOT молчать по этим причинам.
`SCHEMA_MISMATCH`; объект S3 не читается → `FILE_READ_ERROR`; исключение парсера → `UNKNOWN_ERROR` (сообщение без
трассировки). Сервис MUST NOT падать по этим причинам. `SCHEMA_MISMATCH` SHALL публиковаться (без ack), когда из тела
читаемы `job_id` и `file_id` (uuid), а `attempt` — из тела или из заголовка запроса `Nats-Msg-Id = <job_id>:<attempt>`,
и этот `job_id` сервису ещё не известен (не в работе и не среди обработанных). Иначе валидный конверт собрать нельзя
либо он ушёл бы под тем же `Nats-Msg-Id`, что и настоящий результат: сервис SHALL записать в лог ошибку
(`error_category=schema_mismatch`, причина) — уровнем error, если идентификаторы не читаемы, уровнем warning, если
`job_id` уже известен, — и MUST NOT публиковать ни ack, ни результат; ядро обнаружит такое задание по `dlq`/`timeout`
(явная запись в контракт — открытый вопрос «невалидный запрос без читаемых `job_id`/`file_id`/`attempt`» в
`open-questions-proposal.md`). FILE_READ_ERROR и UNKNOWN_ERROR публикуются всегда: сервис MUST NOT молчать по ним.
#### Scenario: Нет объекта в S3
- **WHEN** `s3.key` запроса указывает на несуществующий объект
@ -60,6 +67,17 @@ Stateless-сервис разбора табличных файлов (XLS/XLSX/
- **WHEN** запрос содержит `schema_version = "2.0"`
- **THEN** публикуется конверт `error.code = SCHEMA_MISMATCH`, ack не публикуется
#### Scenario: Невалидный запрос без читаемых идентификаторов
- **WHEN** тело запроса не JSON либо в нём не читаются `job_id` или `file_id`, либо `attempt` нет ни в теле, ни в
заголовке `Nats-Msg-Id`
- **THEN** в лог пишется ошибка `error_category=schema_mismatch`, ни ack, ни результат не публикуются, процесс
продолжает работу
#### Scenario: Невалидный запрос с уже известным `job_id`
- **WHEN** запрос не проходит схему, а его `job_id` уже в работе или среди обработанных
- **THEN** в лог пишется предупреждение `error_category=schema_mismatch`, ни ack, ни результат не публикуются;
обработка или результат задания с этим `job_id` не меняются
### Requirement: Парсер — заменяемый интерфейс
Разбор файла SHALL выполняться через единый интерфейс «байты + тип файла → документы/метрики/ошибка», не знающий о
NATS и S3; воркер MUST NOT зависеть от конкретной реализации. В Срезе 0 реализация SHALL возвращать `NOT_IMPLEMENTED`