diff --git a/openspec/changes/archive/2026-08-25-remove-frontend-hook-token-params/.openspec.yaml b/openspec/changes/archive/2026-08-25-remove-frontend-hook-token-params/.openspec.yaml new file mode 100644 index 0000000..475fddd --- /dev/null +++ b/openspec/changes/archive/2026-08-25-remove-frontend-hook-token-params/.openspec.yaml @@ -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 diff --git a/openspec/changes/archive/2026-08-25-remove-frontend-hook-token-params/design.md b/openspec/changes/archive/2026-08-25-remove-frontend-hook-token-params/design.md new file mode 100644 index 0000000..f5e631b --- /dev/null +++ b/openspec/changes/archive/2026-08-25-remove-frontend-hook-token-params/design.md @@ -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 + + diff --git a/openspec/changes/archive/2026-08-25-remove-frontend-hook-token-params/proposal.md b/openspec/changes/archive/2026-08-25-remove-frontend-hook-token-params/proposal.md new file mode 100644 index 0000000..5f6c0ba --- /dev/null +++ b/openspec/changes/archive/2026-08-25-remove-frontend-hook-token-params/proposal.md @@ -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 не обнаружит фактическую +документационную дельту. diff --git a/openspec/changes/archive/2026-08-25-remove-frontend-hook-token-params/tasks.md b/openspec/changes/archive/2026-08-25-remove-frontend-hook-token-params/tasks.md new file mode 100644 index 0000000..78a8900 --- /dev/null +++ b/openspec/changes/archive/2026-08-25-remove-frontend-hook-token-params/tasks.md @@ -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 не изменились.