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,8 @@
schema: wealth-ledger-sdd
created: 2026-08-25
goal: Убрать accessToken из публичных сигнатур frontend entity hooks и
централизовать auth gating.
skip_specs: true
affected_areas:
- frontend
- specs
@@ -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 -->
@@ -0,0 +1,62 @@
## Why
Frontend data-loading code сейчас заставляет page-level callers вручную
передавать текущий session access token в несколько investment data hooks,
хотя сам bearer header уже добавляется централизованным authenticated
transport boundary. Из-за этого token одновременно является security credential
и локальным флагом включения запроса в местах, которые не должны владеть
transport concerns.
Это стоит исправить сейчас, потому что review от 25 авг 2026 подтвердил
повторяющийся token-plumbing pattern как отдельную frontend architecture
проблему. Пока он остаётся в публичных hook interfaces, новые screens будут
копировать тот же контракт и расширять область, где auth state виден без
необходимости.
## What Changes
- Сузить frontend data-loading interfaces так, чтобы callers investment views
больше не передавали session access token через props или hook arguments для
включения query.
- Централизовать authenticated-query gating на frontend API/auth boundary,
сохранив существующую transport responsibility: bearer injection остаётся
единственным местом, которое формирует Authorization header для защищённых
backend requests.
- Сохранить текущее observable behavior для authenticated и unauthenticated
users: защищённые investment requests не выполняются без active session, а
при active session продолжают использовать тот же backend API contract и
тот же refresh/retry behavior.
- Сохранить existing query cache policy, stale behavior, loading/error/empty
presentation states и generated OpenAPI snapshot.
- Non-goal: менять backend auth semantics, HTTP routes, response DTOs,
generated API types, session storage model или T-Bank connection behavior.
- Non-goal: выполнять несвязанные frontend page restructuring или error-code
consolidation из architecture review.
## Capabilities
### New Capabilities
_Нет._
### Modified Capabilities
_Нет._
Это behavior-preserving frontend architecture cleanup. Change отказывается от
Delta Specs через `skip_specs: true`, потому что user-visible capability,
backend API contract, durable data behavior и product requirement не меняются.
Если при Design или реализации обнаружится observable behavior change, работа
должна вернуться к Proposal и Delta Specs до продолжения.
## Impact
Users должны видеть те же investment screens и auth transitions до и после
change. Frontend maintainers и AI agents получают более узкий data-loading
interface: session token остаётся detail auth/API boundary, а не параметром
каждого investment data consumer.
Affected implementation area — frontend authenticated data-loading boundary и
его callers. Specs отслеживает SDD lifecycle. Backend и as-built docs
repository не должны меняться, если Design не обнаружит фактическую
документационную дельту.
@@ -0,0 +1,24 @@
## 1. Шов Authenticated Query
- [x] 1.1 Добавить focused failing frontend test для protected query gating в `frontend/src/shared/api`: protected query не вызывает MSW endpoint без configured session и загружается после `configureAuthClient` с access token; плановая проверка: `npm run test:run -- src/shared/api`.
- [x] 1.2 Реализовать минимальный shared authenticated-query seam в `frontend/src/shared/api`: subscribed auth snapshot конфигурируется существующим auth client setup, а `enabled` вычисляется как `hasSession && callerEnabled`; плановая проверка: `npm run test:run -- src/shared/api`.
- [x] 1.3 Проверить, что `frontend/src/shared/api/client.test.ts` по-прежнему доказывает bearer injection, refresh retry, clearing после failed refresh и исключение auth endpoints; плановая проверка: `npm run test:run -- src/shared/api/client.test.ts`.
## 2. Интерфейсы Entity Hooks
- [x] 2.1 Refactor `useBrokerageAccounts`: убрать `accessToken` argument, использовать shared authenticated-query seam и сохранить query key, `staleTime: 60_000`, `retry: false`; плановая проверка: `npm run typecheck`.
- [x] 2.2 Refactor `usePortfolio`: оставить только `accountId`, использовать authenticated query enablement вместе с `Boolean(accountId)`, сохранить path params, `staleTime: 30_000`, `retry: false`; плановая проверка: `npm run test:run -- src/entities/portfolio/api/usePortfolio.test.tsx`.
- [x] 2.3 Refactor `useBrokerageOperations`: оставить `accountId` и query object, сохранить `brokerageOperationsQueryParams`, sorted categories, path/query params, `staleTime: 30_000`, `retry: false`; плановая проверка: `npm run test:run -- src/entities/broker-operation/api/useBrokerageOperations.test.tsx`.
- [x] 2.4 Refactor `useBrokerageEvents`: оставить только `accountId`, использовать authenticated query enablement вместе с `Boolean(accountId)`, сохранить path params, `staleTime: 30_000`, `retry: false`; плановая проверка: добавить или обновить focused hook test и запустить `npm run test:run -- src/entities/brokerage-event`.
## 3. Call Sites И Существующие Tests
- [x] 3.1 Обновить accounts page ViewModel, account page ViewModel, account operations page, account events page и breadcrumbs на новые domain-shaped hooks; убрать `useAuth()` reads, которые существовали только для передачи `accessToken`; плановая проверка: из `frontend` команда `rg "useBrokerageAccounts\\(|usePortfolio\\(|useBrokerageOperations\\(|useBrokerageEvents\\(" src/pages src/app src/entities -n` показывает обновлённые call sites без token argument pattern.
- [x] 3.2 Обновить frontend tests и mocks, затронутые изменением hook interfaces, сохранив existing user-visible state expectations; плановая проверка: `npm run test:run -- src/pages/accounts src/pages/account src/pages/account-operations src/pages/account-events src/app/ui`.
- [x] 3.3 Подтвердить, что review item закрыт без расширения scope на `useTbankConnectionViewModel` или error-code consolidation; плановая проверка: из `frontend` команда `rg "usePortfolio\\([^\\n,]+,\\s*accessToken|useBrokerageAccounts\\(accessToken|useBrokerageOperations\\([^\\n]+accessToken|useBrokerageEvents\\([^\\n]+accessToken" src -n` не находит matches.
## 4. Verification
- [x] 4.1 Запустить frontend canonical gate после реализации; плановая проверка: `npm run verify` из `frontend`.
- [x] 4.2 Запустить OpenSpec structural validation для change; плановая проверка: `openspec validate remove-frontend-hook-token-params --type change --strict --store wealth-ledger-specs` из `specs`.
- [x] 4.3 Запустить workspace dispatcher для change и выполнить semantic verification против Proposal, Design и completed Tasks; плановая проверка: `bash specs/scripts/verify-workspace-change.sh remove-frontend-hook-token-params` из workspace root плюс recorded read-through, что user-visible behavior, backend contract и out-of-scope frontend architecture work не изменились.