diff --git a/openspec/changes/slice-0-skeleton/proposal.md b/openspec/changes/slice-0-skeleton/proposal.md index 63f806a..95ad200 100644 --- a/openspec/changes/slice-0-skeleton/proposal.md +++ b/openspec/changes/slice-0-skeleton/proposal.md @@ -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`. diff --git a/openspec/changes/slice-0-skeleton/specs/tabular-recognition-service/spec.md b/openspec/changes/slice-0-skeleton/specs/tabular-recognition-service/spec.md index 55fca3c..3dec6ba 100644 --- a/openspec/changes/slice-0-skeleton/specs/tabular-recognition-service/spec.md +++ b/openspec/changes/slice-0-skeleton/specs/tabular-recognition-service/spec.md @@ -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` сервису ещё не известен (не в работе и не среди обработанных). Иначе валидный конверт собрать нельзя +либо он ушёл бы под тем же `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`