Files

12 KiB
Raw Permalink Blame History

remove-frontend-hook-token-params — Design

Context

Защищённые frontend hooks для investment-данных сейчас выставляют session accessToken как часть caller-facing interface только для вычисления React Query enabled. Сам Authorization header уже принадлежит frontend/src/shared/api/client.ts: AuthProvider конфигурирует shared client token/refresh/clear handlers, authenticatedFetch добавляет bearer headers для non-auth endpoints и выполняет существующий single retry после UNAUTHORIZED.

Повторяющееся token plumbing видно в entity hooks для Brokerage Accounts, Portfolio, Account Ledger и Future Cash Events, а также в их callers: accounts/account page ViewModels, account operations/events pages и breadcrumbs. Из-за этого page-level modules знают transport credential, который больше нигде не используют.

FSD-направление импортов важно для выбора seam. Entity hooks могут зависеть от shared/api, но не должны импортировать features/auth. Поэтому auth-aware query interface нельзя реализовать через прямой useAuth() внутри entity hooks. Design должен сохранить поток auth configuration из features/auth в shared/api, а entity hooks должны потреблять только shared/api.

Frontend-ограничения: React/Vite, TanStack Query, openapi-react-query, MSW-backed Vitest tests, strict TypeScript, dependency-cruiser FSD boundaries, canonical gate npm ci && npm run verify под Node 22. Backend routes, generated OpenAPI snapshot и user-visible behavior не меняются.

Goals / Non-Goals

Goals:

  • Убрать session token parameters из interfaces защищённых investment entity hooks.
  • Централизовать authenticated query enablement в shared/api, рядом с существующим authenticated transport seam.
  • Сохранить bearer injection, refresh/retry behavior, cache keys, stale policies, retry policies и page state behavior.
  • Сохранить валидное FSD-направление зависимостей: entity hooks зависят от shared API/auth query utilities, а не от auth feature.
  • Добавить focused regression proof, что protected queries остаются disabled без active session и загружаются нормально, когда session появляется.

Non-Goals:

  • Менять backend auth semantics, HTTP routes, DTOs, generated OpenAPI files или T-Bank behavior.
  • Переносить auth feature целиком в shared или менять login/register/logout UX.
  • Refactor useTbankConnectionViewModel; там есть похожий локальный enabled: Boolean(accessToken) pattern, но он находится вне четырёх entity hook interfaces, названных этим review item.
  • Перестраивать account page VM/view, account operations/events pages или breadcrumbs за пределами удаления token plumbing, нужного из-за изменения hook interfaces.
  • Консолидировать дублированные frontend error-code helpers.

Decisions

D1. Ввести shared authenticated-query module. shared/api получает небольшой authenticated-query interface, который оборачивает TanStack useQuery и совмещает обычные query options вызывающего кода с session-aware enablement. Protected entity hooks вызывают этот interface вместо того, чтобы принимать accessToken и самостоятельно выставлять enabled. Endpoint-specific hooks по-прежнему владеют path/query parameters, cache keys, stale times и retry policy.

Альтернатива: оставить accessToken hook parameter и почистить только callers. Отклонено: shallow interface остаётся видимым, поэтому новые callers продолжат изучать token plumbing как обязательный pattern.

D2. Auth state передаётся в shared API через внутренний snapshot, а не через импорт useAuth. features/auth продолжает конфигурировать shared/api current token и refresh handlers. Shared API layer дополнительно публикует минимальный authenticated-session snapshot/subscription, который authenticated queries используют для вычисления enabled. Это сохраняет направление зависимостей features -> shared и entities -> shared, без импортов entities -> features или shared -> features.

Альтернатива: разрешить entity hooks вызывать useAuth(). Отклонено: entities начали бы зависеть вверх от feature slice и нарушили бы documented FSD direction.

D3. Compose, а не replace, caller enablement. Authenticated query enablement вычисляется как hasSession && callerEnabled, где callerEnabled по умолчанию равен true. Account-scoped hooks сохраняют domain precondition, передавая Boolean(accountId) как caller enablement. Это сохраняет текущее поведение: undefined account id не создаёт request, а unauthenticated state подавляет все protected investment requests.

Альтернатива: проверять в authenticated query только session и полагаться на empty path params при отсутствующем account id. Отклонено: это превратило бы текущую disabled-query ветку в невалидный protected request.

D4. Transport retry остаётся независимым от query gating. Authenticated query module только решает, может ли protected query стартовать. Он не строит headers, не запускает refresh напрямую и не интерпретирует 401 responses. Существующий authenticatedFetch остаётся единственным owner bearer injection и refresh/retry path для requests, которые уже стартовали.

D5. Protected entity hook interfaces становятся domain-shaped. Caller-facing interfaces после изменения описываются потребностями данных: accounts list не имеет arguments, portfolio/events принимают optional account id, operations принимает optional account id и query object. Page/ViewModel modules перестают читать accessToken, если он был нужен только для передачи в hook.

D6. Tests покрывают seam, а не token plumbing. Regression tests должны доказывать observable behavior через shared authenticated-query seam: protected query не попадает в MSW без configured session, и тот же hook загружается, когда session exists. Existing endpoint tests обновляются под новые interfaces и продолжают проверять query parameter normalization и successful endpoint paths. Tests не должны grep-ить source, чтобы доказать исчезновение token argument; TypeScript и компиляция call sites покрывают interface change.

Delivery Map

Area Status Architectural outcome or N/A reason
frontend affected shared/api владеет authenticated query enablement; четыре protected investment entity hooks убирают accessToken parameters; callers обновлены на domain-shaped hook interfaces; focused tests покрывают no-session gating и session-enabled loading.
backend N/A Backend auth, routes, DTOs, T-Bank integration и runtime OpenAPI не меняются.
specs affected Этот SDD change хранит approved Proposal, skipped Delta Specs, Design и Tasks для behavior-preserving frontend architecture cleanup.
docs N/A As-built product docs не меняются, потому что user-visible behavior и system contract остаются прежними; frontend-local guidance уже фиксирует, что bearer headers принадлежат shared authenticated fetch.

Contract Gate Decision

Gate Status Reason
runtime-openapi N/A Change не затрагивает backend или frontend/backend API contract, generated client или OpenAPI snapshot.

Solution Assessment

Формальная build-versus-buy assessment не triggered. Change не добавляет и не заменяет authentication, security, storage, external integration, observability, caching, concurrency control или infrastructure. Он сохраняет существующий auth mechanism и TanStack Query/openapi-react-query stack, перенося project-owned query enablement за меньший in-process interface.

Risks / Trade-offs

  • Session reactivity regression. Если shared auth snapshot не уведомляет authenticated queries при изменении token state, queries могут остаться disabled после login/refresh или enabled после logout. Митигация: реализовать snapshot как subscribed external store и покрыть disabled/enabled transition behavior tests.
  • FSD boundary regression. Чтение auth state из entity hooks через features/auth исправило бы симптом, но нарушило architecture. Митигация: разместить seam в shared/api и опираться на существующий dependency-cruiser gate.
  • Over-wide shared API interface. Helper, который зеркалит все TanStack Query options, может стать shallow. Митигация: держать его узко ответственным за composition обычных useQuery options с authenticated enablement; endpoint hooks сохраняют endpoint-specific policy.
  • Похожий pattern остаётся в T-Bank connection VM. Change намеренно оставляет один feature-local use of accessToken gating без изменений. Митигация: зафиксировать это как out of scope; отдельный будущий change может решить, должны ли feature-level status queries тоже переехать на shared authenticated-query seam.

Migration Plan

  1. Добавить shared authenticated-query module и tests для no-session disabled behavior и session-enabled loading.
  2. Refactor Brokerage Accounts, Portfolio, Account Ledger и Future Cash Events entity hooks на shared authenticated-query interface, сохранив query keys, request parameters, stale times и retry policies.
  3. Обновить все callers и tests на новые domain-shaped hook interfaces, убрав useAuth() reads, которые существовали только для передачи accessToken.
  4. Запустить focused frontend tests для изменённых hooks/callers, затем frontend canonical gate npm run verify.
  5. Запустить openspec validate remove-frontend-hook-token-params --type change --strict --store wealth-ledger-specs, выполнить semantic verification против Proposal/Design/Tasks и использовать workspace dispatcher перед archive.

Откат — обычный frontend/specs code revert. Data migration, OpenAPI sync, backend migration и feature flag не вводятся.

Open Questions