Conventional commits / commits (pull_request) Successful in 14s
Docs security / security (pull_request) Successful in 8s
Docs verify / verify (pull_request) Successful in 13s
Docs security / security (push) Successful in 8s
Docs verify / verify (push) Successful in 12s
Document local data ownership, coverage and freshness, refresh behavior, failure recovery and additive migration rollback. Record ADR 017.
112 lines
8.8 KiB
Markdown
112 lines
8.8 KiB
Markdown
# HTTP API и ошибки
|
||
|
||
## Источник контракта
|
||
|
||
Backend генерирует OpenAPI из собственного публичного API. Frontend использует
|
||
предоставленный OpenAPI snapshot как актуальный вход и локально генерирует
|
||
типизированный клиент. Backend владеет контрактом, а подготовка и доставка
|
||
схемы находятся вне frontend. Это разделение принято в
|
||
[ADR 002](../adr/002-nest-generated-openapi-contract.md) и
|
||
[ADR 014](../adr/014-trusted-openapi-input.md).
|
||
|
||
Backend командой `npm run openapi:generate` создаёт `openapi.json` без запуска
|
||
сервера и не хранит результат в Git. Contract test сравнивает вывод генератора
|
||
с Nest runtime schema настроенного приложения. Frontend хранит полученный
|
||
artifact у себя и проверяет соответствие generated types этой копии. CI не
|
||
скачивает соседние репозитории и не сравнивает их текущие revisions. Обновление
|
||
входного artifact потребителя выполняется отдельно с review; совместимость
|
||
развёрнутых версий не доказывается одним зелёным локальным CI.
|
||
|
||
API имеет prefix `/api/v1`; Swagger публикуется для локальной разработки по
|
||
environment policy, а не используется frontend в runtime.
|
||
|
||
## Инвестиционные данные и синхронизация
|
||
|
||
T-Bank data GET возвращают envelope `{ data, meta }`. `data` может быть `null`
|
||
при missing или неполном финансовом агрегате; partial ledger/candles могут
|
||
содержать подтверждённые строки. `meta` разделяет availability, freshness,
|
||
lastSuccessfulAt, sync, coverage и состояния зависимостей. `dataRevision`
|
||
описывает набор данных, `stateVersion` — наблюдаемое состояние, включая
|
||
пустое завершённое покрытие. Ни одно поле не раскрывает connection revision.
|
||
|
||
Обычный GET планирует missing/stale зависимости после проверки доступа.
|
||
GET с `sync=observe` только читает. `POST /api/v1/tbank-sync` принимает
|
||
валидированный screen descriptor и возвращает `202` с handle и нормализованным
|
||
scope. `GET /api/v1/tbank-sync/:id` наблюдает разрешённую работу; чужой или
|
||
прежний handle возвращает `404`. Приём работы не означает её успех.
|
||
|
||
Source failure принятой работы передаётся безопасным `meta.sync.errorCode`;
|
||
HTTP transport/access/input errors сохраняют Problem Details. Старые данные
|
||
при transient failure сохраняют исходное успешное время. Детали поведения —
|
||
[свежесть и полнота](cross-cutting/data-freshness.md).
|
||
|
||
Envelope несовместим с прежними bare-array ответами: backend/frontend
|
||
поставляются согласованной парой. Финальный artifact передаётся явно;
|
||
локальная генерация и CI frontend читают только собственный snapshot.
|
||
|
||
## Validation и identity
|
||
|
||
Backend преобразует и проверяет входные данные на общей HTTP-границе.
|
||
Неизвестные свойства запроса удаляются; сам факт их передачи не обещает отказа.
|
||
Проверка формата не заменяет бизнес-ограничения и проверку доступа к счёту.
|
||
Аутентификация подтверждает личность пользователя; accountId из URL сам по себе
|
||
не доказывает доступ к брокерским данным.
|
||
|
||
## Error contract
|
||
|
||
Общая граница обработки HTTP-ошибок возвращает `application/problem+json`
|
||
со `status` и публичным `code`.
|
||
Неожиданный exception возвращает `500` / `INTERNAL_SERVER_ERROR`, без исходного
|
||
message/stack. Типичный ответ:
|
||
|
||
```json
|
||
{ "status": 401, "code": "UNAUTHORIZED" }
|
||
```
|
||
|
||
Для input validation контракт требует `400`, `VALIDATION_ERROR` и `errors` как
|
||
массив `{ pointer, detail }`, где pointer адресует поле запроса. Это требование
|
||
не считается проверенным одним описанием OpenAPI: нужна проверка реального
|
||
HTTP-ответа после всех стадий обработки запроса.
|
||
|
||
**Известное расхождение:** согласованная
|
||
[спецификация аутентификации](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/user-authentication/spec.md#requirement-стандартные-ответы-об-ошибках)
|
||
требует `pointer` и `detail` для невалидных полей. В текущей конфигурации
|
||
валидация формирует строковые сообщения, а фильтр ожидает структурированные
|
||
ошибки полей; поэтому корректность деталей `errors` не подтверждена.
|
||
Исправление требует отдельного изменения реализации и проверки полного
|
||
HTTP-потока. OpenAPI сам по себе этого расхождения не устраняет.
|
||
|
||
Ответ не включает автоматически все поля RFC (`type`, `title`, `instance`).
|
||
Добавление обязательного поля является изменением публичного контракта.
|
||
Клиент выбирает поведение по публичному коду, а не по внутреннему тексту ошибки.
|
||
|
||
## Domain errors и auth
|
||
|
||
Специальный code определяется сценарием; общее преобразование ошибок и безопасная
|
||
форма ответа принадлежат HTTP-границе. Auth endpoints используют свои status и cookie
|
||
operations: [Authentication](../domains/authentication/overview.md).
|
||
T-Bank token failure и временная недоступность имеют разные значения:
|
||
[Connection API](../integrations/tbank.md#состояние-и-ошибки). Raw upstream/Prisma errors
|
||
и secrets не передаются frontend.
|
||
|
||
## Наблюдаемость
|
||
|
||
Backend пишет структурированные JSON-события в stdout через Pino. Обработка
|
||
HTTP-запроса получает серверный request ID, который возвращается заголовком
|
||
`X-Request-ID`; допустимый входящий идентификатор сохраняется отдельно как
|
||
client request ID. Это позволяет сопоставить публичную ошибку с обработкой
|
||
запроса без передачи внутреннего exception в ответ API.
|
||
|
||
Журнал завершения запроса содержит шаблон маршрута, метод, статус,
|
||
длительность и при наличии публичный error code. События внешней границы
|
||
добавляют операцию, результат и число попыток. Разрешённые поля ограничены;
|
||
полные запросы и ответы, token, cookie и Authorization не являются данными
|
||
журнала. Фильтрация полей и redaction не защищают секрет, вручную включённый
|
||
в свободный текст сообщения или stack.
|
||
|
||
Выбор logger и граница ответственности описаны в
|
||
[ADR 008](../adr/008-pino-backend-logging.md); согласованное поведение — в
|
||
workspace-спецификации
|
||
[Structured logging](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/backend-structured-logging/spec.md).
|
||
Сборщик, срок хранения журналов и alerting не определяются приложением.
|