180 lines
12 KiB
Markdown
180 lines
12 KiB
Markdown
# 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 -->
|