12 KiB
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 обычных
useQueryoptions с authenticated enablement; endpoint hooks сохраняют endpoint-specific policy. - Похожий pattern остаётся в T-Bank connection VM. Change намеренно
оставляет один feature-local use of
accessTokengating без изменений. Митигация: зафиксировать это как out of scope; отдельный будущий change может решить, должны ли feature-level status queries тоже переехать на shared authenticated-query seam.
Migration Plan
- Добавить shared authenticated-query module и tests для no-session disabled behavior и session-enabled loading.
- Refactor Brokerage Accounts, Portfolio, Account Ledger и Future Cash Events entity hooks на shared authenticated-query interface, сохранив query keys, request parameters, stale times и retry policies.
- Обновить все callers и tests на новые domain-shaped hook interfaces, убрав
useAuth()reads, которые существовали только для передачиaccessToken. - Запустить focused frontend tests для изменённых hooks/callers, затем
frontend canonical gate
npm run verify. - Запустить
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 не вводятся.