Files
specs/openspec/changes/archive/2026-08-24-align-api-contract-and-client-resilience/design.md
T

8.6 KiB
Raw Blame History

Контекст

Мотивация изложена в proposal.md, а поведенческий contract — в delta specs. Backend документирует 200 через OpenAPI decorators, но Nest использует стандартный 201 для трёх auth POST handlers, поскольку их runtime status не задан явно. Runtime OpenAPI verifier сравнивает два документа, поэтому не может обнаружить это расхождение HTTP responses.

Во frontend Accounts и Account view models выбирают блокирующий loading/error state до проверки cached query data. У auth provider два пути refresh: cold start напрямую вызывает API без catch, а общий refresh может отклонить Promise при повторе защищённого запроса.

Цели и исключения

Цели:

  • Согласовать фактические auth success responses, OpenAPI, snapshot и generated types на 200 с детерминированным regression gate без Prisma или базы данных.
  • Отображать существующие данные Accounts и Portfolio во время refetch и после его ошибки с доступным локализованным warning и retry action.
  • Дать cold start и повтору защищённого запроса единый non-throwing результат refresh, сохранив текущие serialization и защиту routes.
  • Фиксировать active remediation и обновлять feature/development документацию только фактически поставленным поведением.

Исключения:

  • Исправлять Prisma migration/e2e infrastructure или создавать workspace-wide verifier, отложенный как WL-P2-05 и WL-P2-13.
  • Менять refresh-cookie attributes, token lifetime, session rotation, unauthorised-error codes, auth routes, cache TTL, refetch triggers или backend Portfolio fallback policy.
  • Распространять Client Cached Snapshot на постраничные operations/events, чьи cursor и filters могут определять иной result set.

Решения

Явно установить 200 для трёх auth handlers успешного ответа

Handlers register, login и refresh получают явный HTTP 200, сохраняя существующие ApiOkResponse и payload/cookie code. Frontend artifacts остаются сгенерированными из backend document: запускается штатный sync workflow, а artifact коммитится только если этот workflow создаёт изменение; ручное редактирование generated schema запрещено.

Небольшой независимый от БД backend test проверяет status metadata каждого handler. Он защищает decorator, управляющий реальным Nest output, пока Prisma-backed e2e gate остаётся deferred. Существующие e2e assertions меняются с 201 на 200, но не являются terminal evidence до восстановления Prisma bootstrap.

Отклонённые альтернативы: неявный 201 потребовал бы большего перехода snapshot/types и противоречил бы согласованному единообразному contract; опора только на runtime OpenAPI comparison повторила бы исходную blind spot.

Определять представление refetch по наличию данных, а не isFetching или isError

Accounts и Account view models сначала определяют наличие Client Cached Snapshot. Без данных они сохраняют текущие loading, error и empty views. При наличии данных они возвращают loaded/empty presentation с опциональной информацией об обновлении и его ошибке. Page components сначала отображают данные, добавляют неблокирующий progress indicator во время fetch и передают локализованное warning/retry action в loaded presentation после ошибки.

Account view сохраняет backend warning Portfolio.isStale отдельно от client warning об ошибке refresh. Оба могут отображаться, когда независимо истинны. Новая терминология документируется в CONTEXT.md; это не новое API field и не интерпретация server freshness.

Отклонённые альтернативы: полный error/loading view после refetch скрывает уже полезные финансовые данные; применение правила к operations/events могло бы показать прошлую cursor/filter page как запрошенный результат.

Сделать общий refresh возвращающим false при любом неуспешном исходе

Auth provider централизует успешное применение session и локальную очистку session. Single-flight refresh function перехватывает transport rejection API, а также no-data/HTTP failures, очищает token, profile и React Query cache и возвращает false; она всегда освобождает in-flight Promise. Cold start использует эту общую function и устанавливает ready в finally. authenticatedFetch сохраняет existing one-retry behavior: при false он возвращает исходный 401 после очистки, позволяя protected-route state перенаправить на login.

Отклонённые альтернативы: перехват только mount effect оставляет 401 retry path способным отклонить Promise; добавление третьего startup error state меняет согласованный anonymous/login recovery flow.

Риски и компромиссы

  • [Metadata test не является HTTP e2e test] → WL-P2-05 остаётся deferred, существующие e2e expectations обновляются, а реальное HTTP coverage станет обязательным после выполнения Prisma trigger.
  • [Ошибка refresh утрачивает in-memory session] → после cold start браузер не имеет действительного access token; очистка state предотвращает неоднозначный protected UI и следует существующему 401 recovery behavior.
  • [Client warning и backend stale warning могут сосуществовать] → каждому назначается отдельное localized message и проверяется их независимый render.
  • [Generated sync может затронуть frontend artifacts] → он запускается явно только после backend contract change, а итоговый diff проверяется до принятия.

План изменений

  1. Сначала добавить regression tests, затем сделать backend status явным и обновить существующие e2e assertions, не заявляя, что deferred e2e gate прошёл.
  2. Обновить OpenAPI artifacts через штатную sync command, проверить согласованность runtime document и generated client и оставить только намеренный итоговый diff.
  3. Добавить observable frontend tests для состояний refetch cached data и ошибок refresh, затем отрефакторить view models, pages, localized copy и auth provider до их прохождения.
  4. Обновить feature docs и remediation registry, выполнить применимые non-mutating gates и OpenSpec verification до archive.

Rollback затрагивает только код: отмена затронутых commits восстанавливает предыдущий status/UX; data migration, dependency change и очистка persisted state не нужны.