docs(specs): archive frontend auth query gating change

This commit is contained in:
2026-08-25 14:30:05 +03:00
parent 5cc42f30ca
commit 6e2890f86f
4 changed files with 273 additions and 0 deletions
@@ -0,0 +1,179 @@
# 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 -->