Files
specs/openspec/changes/archive/2026-08-25-remove-frontend-hook-token-params/design.md
T

180 lines
12 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.
# 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
<!-- none -->