Срез 0 (issue #1): раздел «Соглашения шаблона» (что оставлено от шаблона команды, что вырезается, что добавляется); change openspec/changes/slice-0-skeleton — proposal, design с таблицей «Decisions made without discussion», спеки и tasks; в openspec/config.yaml строки с двоеточием взяты в кавычки (YAML разбирал их как словари). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
73 lines
6.4 KiB
Markdown
73 lines
6.4 KiB
Markdown
# Spec Delta: tabular-recognition-service
|
||
|
||
## Purpose
|
||
|
||
Stateless-сервис разбора табличных файлов (XLS/XLSX/XLSB): принимает задания из NATS, читает исходник из S3, отдаёт
|
||
результат в S3 и конверт в NATS по контракту v1.0, не имея доступа к БД и фронта. В Срезе 0 разбор заменён заглушкой.
|
||
|
||
## ADDED Requirements
|
||
|
||
### Requirement: Сервис не зависит от БД и фронта
|
||
Репозиторий MUST NOT содержать каталоги `web/`, `alembic/`, `src/db/`, `src/models/`, `src/repositories/`; зависимости
|
||
`SQLAlchemy`, `asyncpg`, `alembic`, `smbprotocol`, `raisa_fastapi_protected_api` MUST NOT присутствовать в
|
||
`requirements.txt`; настройки `OPENBAO.DATABASE` и `OPENBAO.SMB` MUST NOT требоваться для старта.
|
||
|
||
#### Scenario: Старт с минимальным окружением
|
||
- **WHEN** сервис запускается с `.env`, содержащим только `OPENBAO__MINIO__*`, `OPENBAO__NATS__NATS_URL`, `DEBUG=true`
|
||
- **THEN** `GET /healthcheck` отвечает 200 с полями `uptime`, `version`, `nats`
|
||
|
||
### Requirement: Приём задания и ответ по контракту
|
||
Сервис SHALL потреблять `recognition.tabular.request` durable pull-consumer'ом `tabular-parser`; для каждого запроса,
|
||
проходящего `recognition-request.schema.json`, SHALL прочитать объект `s3.key` из бакета `s3.bucket`, вызвать парсер,
|
||
записать файл результата (по `recognition-result.schema.json`) в `results/{file_id}/{job_id}/result.json`, затем
|
||
опубликовать конверт в `recognition.result` (по `recognition-result-message.schema.json`) с заголовком
|
||
`Nats-Msg-Id = result:<job_id>:<attempt>` и `service.name = "tabular-parser"`, и только после публикации подтвердить запрос.
|
||
|
||
#### Scenario: Запрос с заглушкой парсера (Срез 0)
|
||
- **WHEN** опубликован запрос из `docs/contracts/examples/request-tabular.json` с существующим объектом в `uploads/`
|
||
- **THEN** в `recognition.result` появляется конверт `status = error`, `error.code = NOT_IMPLEMENTED`,
|
||
`job_id`/`file_id` из запроса, `metrics.duration_ms ≥ 0`; файл результата может отсутствовать; запрос подтверждён
|
||
|
||
#### Scenario: Ack только после результата
|
||
- **WHEN** публикация конверта завершается ошибкой
|
||
- **THEN** запрос не подтверждается (nak с задержкой), следующая доставка повторяет обработку; дубликат конверта
|
||
отбрасывается по `Nats-Msg-Id`
|
||
|
||
### Requirement: Ошибки транспорта превращаются в результат
|
||
Сервис SHALL отвечать конвертом `status = error` и подтверждать запрос в случаях: тело не проходит схему или `MAJOR`
|
||
версии ≠ 1 → `SCHEMA_MISMATCH`; объект S3 не читается → `FILE_READ_ERROR`; исключение парсера → `UNKNOWN_ERROR`
|
||
(сообщение без трассировки). Сервис MUST NOT падать и MUST NOT молчать по этим причинам.
|
||
|
||
#### Scenario: Нет объекта в S3
|
||
- **WHEN** `s3.key` запроса указывает на несуществующий объект
|
||
- **THEN** публикуется конверт `error.code = FILE_READ_ERROR`, запрос подтверждён, процесс продолжает работу
|
||
|
||
#### Scenario: Несовместимая версия
|
||
- **WHEN** запрос содержит `schema_version = "2.0"`
|
||
- **THEN** публикуется конверт `error.code = SCHEMA_MISMATCH`, запрос подтверждён
|
||
|
||
### Requirement: Парсер — заменяемый интерфейс
|
||
Разбор файла SHALL выполняться через единый интерфейс «байты + тип файла → документы/метрики/ошибка», не знающий о
|
||
NATS и S3; воркер MUST NOT зависеть от конкретной реализации. В Срезе 0 реализация SHALL возвращать `NOT_IMPLEMENTED`
|
||
для всех типов; каталог `api/rules/` SHALL существовать с README о назначении словарей.
|
||
|
||
#### Scenario: Подмена реализации в тесте
|
||
- **WHEN** в тесте воркеру передан парсер, возвращающий один `osvDocument`
|
||
- **THEN** файл результата с этим документом записан в S3, конверт имеет `status = done` и `result_s3` на этот файл
|
||
|
||
### Requirement: Копия контракта идентична источнику
|
||
`api/contracts/*.schema.json` SHALL быть байт в байт равны файлам `../docs/contracts/`; pydantic-модели SHALL валидировать
|
||
все `../docs/contracts/examples/*.json` и порождать JSON, проходящий схему.
|
||
|
||
#### Scenario: CI сервиса
|
||
- **WHEN** выполняются тесты контракта при доступном `../docs` (или `ARM_KA_DOCS`)
|
||
- **THEN** идентичность подтверждена, примеры валидны; при недоступном docs тест идентичности пропущен с предупреждением
|
||
|
||
### Requirement: Соединение с NATS по правилам шаблона
|
||
При пустых `NATS_SEED`/`NATS_JWT` сервис SHALL подключаться без учётных данных, иначе — с nkeys/JWT. Отсутствие stream
|
||
`RECOGNITION` SHALL приводить к повторным попыткам подписки каждые 10 с и `nats = down` в `/healthcheck`, а не к падению.
|
||
|
||
#### Scenario: Stream появился позже
|
||
- **WHEN** сервис стартовал раньше ядра, создавшего stream
|
||
- **THEN** в течение 20 с после создания stream воркер подписан, `nats = ok`
|