Andrey Epifancev 0c04127aec docs: OpenSpec change slice-0-skeleton — сквозной скелет Среза 0
openspec init (config с контекстом docs-репозитория) и change slice-0-skeleton: proposal, design
(11 решений без обсуждения, открытые вопросы к человеку), спеки recognition-contract,
dev-environment, ci-template, tasks в порядке зависимостей между репозиториями. 06-test-data.md §6:
справочники продукта — seed arm-core, не фикстуры. 09-delivery-plan.md: Срез 0 по факту (change'и,
compose в docs/dev, контракт копией без общего пакета, seed).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-03 02:44:14 +04:00

73 lines
6.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Spec Delta: recognition-contract
## Purpose
Контракт обмена между ядром `arm-core` и stateless-сервисами распознавания (`tabular-parser`, `scan-gateway`):
тела сообщений NATS `recognition.{tabular|image}.request` и `recognition.result`, файл результата в S3 и правила
версионирования и идемпотентности. Сервисы не знают БД; ядро — единственный писатель (DECISION-4).
## ADDED Requirements
### Requirement: Сообщение запроса распознавания
Тело сообщения на subject `recognition.tabular.request` или `recognition.image.request` SHALL соответствовать
`docs/contracts/recognition-request.schema.json` v1.0: обязательные поля `schema_version` (`"1.0"`), `job_id` (uuid =
`recognition_job.id`), `file_id` (uuid = `stored_file.id`), `pipeline` (`tabular` | `image`), `file_type`
(`XLS` | `XLSX` | `XLSB` | `PDF` | `PNG` | `JPG`), `original_name`, `s3 {bucket, key}` исходника в роли `uploads`,
`reply_deadline` (date-time, UTC), `attempt` (≥ 1). Subject SHALL соответствовать `pipeline`.
#### Scenario: Публикация запроса ядром
- **WHEN** ядро переводит `recognition_job` из `queued` в `published`
- **THEN** в subject, соответствующий `recognition_job.pipeline`, публикуется сообщение, проходящее схему запроса, с
заголовком `Nats-Msg-Id` = `<job_id>:<attempt>`
#### Scenario: Сообщение не по схеме
- **WHEN** сервис получает запрос, не проходящий схему (нет `s3.key`, неизвестный `file_type`)
- **THEN** сервис публикует результат со `status = error`, `error.code = SCHEMA_MISMATCH` и не падает
### Requirement: Сообщение результата распознавания
Тело сообщения на subject `recognition.result` SHALL соответствовать `docs/contracts/recognition-result-message.schema.json`
v1.0: обязательные поля `schema_version` (`"1.0"`), `job_id`, `file_id`, `status` (`done` | `needs_check` | `error`),
`service {name, version}`; при `status ≠ error` обязательны `result_s3 {bucket, key}` и `result_schema_version`;
при `status = error` обязателен `error {code, message}`; опциональны `raw_s3_key` (только `scan-gateway`) и `metrics`.
Поле `group_schema` в конверт NOT включается — источник `recognition_job.group_schema` — файл результата.
#### Scenario: Успешный результат
- **WHEN** сервис завершил обработку файла
- **THEN** файл результата записан в S3 по ключу `results/{file_id}/{job_id}/result.json`, а затем опубликован конверт
со `status = done | needs_check`, `result_s3` указывает на этот файл, заголовок `Nats-Msg-Id` = `result:<job_id>:<attempt>`
#### Scenario: Ошибка обработки
- **WHEN** сервис не смог обработать файл (нечитаем, таймаут внешней системы, тип не поддерживается)
- **THEN** публикуется конверт со `status = error` и `error.code` из перечня схемы (`UNSUPPORTED_FORMAT`, `UNREADABLE`,
`TIMEOUT`, `SCHEMA_MISMATCH`, `NOT_IMPLEMENTED`, `FILE_READ_ERROR`, `INVALID_FORMAT`, `INVALID_DOCUMENT`,
`RECOGNITION_FAILED`, `UNKNOWN_ERROR`); `result_s3` может отсутствовать
### Requirement: Файл результата
Файл результата SHALL соответствовать `docs/contracts/recognition-result.schema.json` v1.0: один файл (`stored_file`)
→ один `job_id` → массив `documents` из `osvDocument` и/или `statementDocument`; денежные значения — строки
`^-?\d+(\.\d{1,2})?$`; `confidence` — 0–100. Пустой массив `documents` допустим только при `status = error`.
#### Scenario: Результат валидируется примерами
- **WHEN** CI сервиса запускает тесты контракта
- **THEN** все файлы `docs/contracts/examples/result-*.json` проходят схему файла результата, а все
`request-*.json` / `result-message-*.json` — схемы сообщений
### Requirement: Версионирование контракта
Поле `schema_version` SHALL быть строкой `MAJOR.MINOR`. Добавление необязательных полей или значений enum —
`MINOR` (1.1, обратно совместимо); удаление/переименование полей, смена типов или семантики — `MAJOR` (2.0).
Сервис SHALL отвергать запрос с неизвестным `MAJOR` (`error.code = SCHEMA_MISMATCH`); ядро SHALL принимать результат
с тем же `MAJOR` и любым известным или новым `MINOR`.
#### Scenario: Несовместимая версия запроса
- **WHEN** сервис, поддерживающий `1.x`, получает запрос `schema_version = "2.0"`
- **THEN** публикуется результат `status = error`, `error.code = SCHEMA_MISMATCH`, запрос подтверждается (ack), чтобы не
зациклить доставку
### Requirement: Копия контракта в сервисе идентична источнику
Каждый сервис, читающий или пишущий контракт, SHALL хранить копию файлов схем в `api/contracts/` и проверять в CI их
байтовую идентичность файлам `docs/contracts/` (checkout `arm-ka-docs` или путь `ARM_KA_DOCS`).
#### Scenario: Копия устарела
- **WHEN** в `docs/contracts/` изменена схема, а копия в сервисе — нет
- **THEN** тест идентичности в CI сервиса падает с указанием имени файла