docs(specs): archive frontend auth query gating change
This commit is contained in:
@@ -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 не изменились.
|
||||
Reference in New Issue
Block a user