diff --git a/.ai/CONVENTIONS.md b/.ai/CONVENTIONS.md deleted file mode 100644 index 73d81bd..0000000 --- a/.ai/CONVENTIONS.md +++ /dev/null @@ -1,25 +0,0 @@ -# Backend router - -Объединяй triggers из смысла задачи, путей и обнаруженных boundaries. -Читай выбранные guides до решения; якорь допускает чтение только нужного раздела. -Просто backend-файл не требует всех conventions. - -| Решение / сигнал | Читать | -| --- | --- | -| Новый файл/suffix/layer; поиск feature owner; progressive structure; перенос responsibility | [Architecture](conventions/backend-architecture.md) | -| TS vocabulary, domain literals, magic values | [Domain literals](conventions/backend-architecture.md#domain-literals-and-magic-values) | -| Controller, DTO, route/status, validation/exception, публичная HTTP-семантика | [HTTP API](conventions/backend-http-api.md) | -| Repository/data access; Prisma/schema/query, transaction, migration, atomic write | [Data](conventions/backend-data.md) | -| Тесты или behavior с testing risk | [Testing](conventions/backend-testing.md) | -| JWT, cookie, login/refresh/logout, guard/access, token encryption, sensitive logs | [Security](conventions/security-auth.md) | -| External client/integration boundary; T-Bank/gRPC/proto, retry/rate limit/cache | [Integrations](conventions/backend-integrations.md) | -| Logging/request context без изменения secrets/auth | [Logging](conventions/backend-architecture.md#logging) | -| Новый abstraction/dependency, refactor, неясный scope | [Общий выбор реализации](../../AGENTS.md) | -| Изменение request/response/route/status/error shape или Swagger metadata | HTTP API + [Источник HTTP-контракта](../../docs/architecture/http-api.md#источник-контракта) | - -Новый endpoint обычно требует HTTP API + Testing; Architecture/Data/Security -добавляются по новым ответственности, persistence или access rules. -Изменение одного Prisma query без HTTP/auth границы не требует HTTP API/Security. -Auth refresh активирует Security + Data + Testing; HTTP API добавляется, если -меняется controller/HTTP contract. Exact docs/spec sources выбирай через SOURCES. -Повтори routing при расширении diff, включая новые и unstaged файлы. diff --git a/.ai/SOURCES.md b/.ai/SOURCES.md deleted file mode 100644 index 8a78838..0000000 --- a/.ai/SOURCES.md +++ /dev/null @@ -1,19 +0,0 @@ -# Источники backend - -| Что нужно установить | Владелец | -| --- | --- | -| Implementation decision и безопасный pattern | [CONVENTIONS](CONVENTIONS.md) | -| HTTP errors, DTO semantics, public wire shape | [docs: HTTP API](../../docs/architecture/http-api.md); runtime OpenAPI из Nest — единственный editable source | -| Auth/session, account access, token encryption | [Аутентификация](../../docs/domains/authentication/overview.md), [доступ к данным](../../docs/architecture/containers.md#хранимые-данные-и-границы-доступа), [подключение брокера](../../docs/integrations/tbank.md) | -| Prisma schema/data ownership и migration context | [docs: Data](../../docs/architecture/containers.md#хранимые-данные-и-границы-доступа) | -| T-Bank/gRPC/cache, logging | [T-Bank](../../docs/integrations/tbank.md), [свежесть данных](../../docs/architecture/cross-cutting/data-freshness.md), [наблюдаемость](../../docs/architecture/http-api.md#наблюдаемость) | -| Money, account/position/income/cache meanings | [docs: Domain](../../docs/glossary.md) | -| Поведение конкретной feature | [docs: Features](../../docs/README.md#предметные-области) | -| Существующие behavioral requirements при необходимости | [specs index](../../specs/AI_INDEX.md) | -| Versions/scripts и scope автоматических checks | package.json/lockfile, ESLint/TypeScript/Jest/Prisma configs | - -SOURCES — карта, не требование прочитать все ссылки. Code показывает реализацию, -ADR — rationale, docs — текущие факты; старый пример не отменяет контракт. -Исследование предлагает варианты, а actual source разрешает выбор. При missing -или conflicting security/data facts сначала установи источник. Для обычной -работы OpenSpec/SDD не требуется. diff --git a/.ai/conventions/backend-architecture.md b/.ai/architecture.md similarity index 71% rename from .ai/conventions/backend-architecture.md rename to .ai/architecture.md index a3e16f1..a9a2311 100644 --- a/.ai/conventions/backend-architecture.md +++ b/.ai/architecture.md @@ -1,12 +1,53 @@ # Backend architecture +## Устройство и data flow + +NestJS + TypeScript modular monolith. Composition root `AppModule` подключает +бизнес-модули, validated configuration, Prisma и Pino. Общая `configureApp` +задаёт `/api/v1`, CORS/cookies, validation, request logging и error filter. +Точные versions/scripts — в [package.json](../package.json); source code +определяет фактическую реализацию. + +```text +HTTP controller → application service/use case → pure domain rules + ↓ + repository/data access → Prisma → SQLite + ↓ + integration client → T-Bank gRPC / MOEX / NCR HTTP +``` + +`modules/auth`, `modules/banking/bank-cards` и области `modules/investments` +владеют пользовательскими возможностями. Сложные модули разделены на +presentation/application/domain/infrastructure; компактные остаются плоскими. +`*.module.ts` — Nest DI, `*.api.ts` — используемые cross-module exports. +Application использует конкретные providers; обязательных ports на каждый +класс нет. + +Stocks/bonds — slices одного instruments module: catalog владеет identity/cache, +market — котировками, income — выплатами/личными позициями. Общая orchestration +в `instruments/application` использует DTO/provider records и не становится pure +domain из-за extraction в функцию. Boundary mappers преобразуют внешние records; +чистые расчёты portfolio/banking остаются у domain owner. + +PrismaService владеет DB lifecycle. Самостоятельные use cases получают данные +через concrete repositories; существующие связные CRUD/transaction services +сохраняют прямые queries по [data convention](conventions/data.md). +Money — currency + decimal string; расчёты используют точные общие helpers. + +Auth управляет identity/session; TbankAccessService проверяет доступ к счёту и +connection generation. Integration clients владеют protocol/deadline/retry; +provider records преобразуются перед чистыми domain calculations. +Пользовательский `tbank-connection` не владеет низкоуровневым gRPC client. + +Runtime OpenAPI из Nest → предоставленный frontend snapshot → generated types. +Problem Details фильтруются общей HTTP boundary; StructuredLogger добавляет +request context и ограничивает поля событий. Jest unit tests, изолированные +SQLite/HTTP suites и OpenAPI parity доказывают разные части поведения. +Системные потоки и ADR выбираются через [Источники](../AGENTS.md#источники). + ## Module-first organization -Backend — domain-oriented modular monolith с лёгким разделением -по ответственности. `src/modules` группирует код по бизнес-возможности: `auth`, `banking/bank-cards`, области `investments`. -В инвестициях отдельные Nest modules владеют `brokerage-accounts`, `portfolio`, -`operations`, `income`, `upcoming-events`, `instruments`, `tbank-connection`. -`instruments/stocks` и `instruments/bonds` — slices внутри одного модуля. +Сначала выбери business owner в `modules/`, затем ответственность внутри него. ### Progressive structure @@ -36,15 +77,6 @@ feature/ Для одного controller или пары DTO дополнительная поддиректория не нужна. Пустые папки, новые barrels, CQRS и interfaces не требуются для этой организации. -В текущем коде слои используют `auth`, `bank-cards`, `portfolio`, `operations` -и slices `instruments/stocks`, `instruments/bonds`. Общие application helpers -инструментов находятся в `instruments/application`; `instruments.module.ts` -компонует оба slice. `stock-position.ts` и `instrument-position.ts` формируют -результат из portfolio DTO; `instrument-market.ts` обрабатывает внешние данные -и ошибки Nest. Это application code, а не независимая domain model. -`brokerage-accounts`, `income`, `upcoming-events`, `tbank-connection` остаются -компактными плоскими modules. Наличие tests само по себе не требует слоёв. - ## Поиск и создание файла При работе над feature проходи владельцев в порядке: @@ -116,7 +148,7 @@ Cross-module consumers во всех строках ограничены пуб `*.decorator.ts`, `*.config.ts`, `*.validation.ts`) сохраняют соответствующие существующие роли в common/config. `tbank.crypto.ts` — локальная реализация шифрования токена; имя не является разрешением создавать generic crypto layer. -HTTP/e2e naming и расположение — в [Testing](backend-testing.md). +HTTP/e2e naming и расположение — в [Testing](conventions/testing.md). Без зафиксированной необходимости не вводи `*.helper.ts`, `*.utils.ts`, `*.manager.ts`, `*.processor.ts`, `*.handler.ts`, `*.provider.ts`, `*.factory.ts`. @@ -134,6 +166,10 @@ Tests могут импортировать внутренний owner для п Новый export требует текущего потребителя; controller и persistence остаются приватными. Не добавляй `index.ts` на каждом уровне. +Circular dependencies устраняй изменением ownership/composition. `forwardRef()` +не является стандартным исправлением boundaries; исключение требует конкретной +неизбежной связи и проверки DI initialization. + Каждый provider регистрируется у одного владельца. Потребители импортируют его module, а не повторяют provider: это сохраняет единственный кэш и лимитер. `AuthModule` экспортирует guard; наличие JWT не заменяет account access check. @@ -162,37 +198,25 @@ Tests могут импортировать внутренний owner для п может использовать application types/input и domain rules; новые interfaces ради инверсии зависимостей не обязательны. Domain не импортирует presentation или infrastructure. Существующие связные CRUD/transaction services могут сохранять прямые queries -по [Data](backend-data.md); use case получает данные через repository и сам +по [Data](conventions/data.md); use case получает данные через repository и сам Prisma queries не выполняет. Entity/interface на каждую таблицу не требуется. DTO остаются транспортными runtime classes; существующее использование DTO -на application boundary допустимо по [HTTP API](backend-http-api.md). +на application boundary допустимо по [HTTP API](conventions/http-api.md). Сохраняй существующий error contract: HTTP exceptions остаются на application/ transport boundary. Чистому расчёту не нужен новый framework ошибок. -Точные ограничения — [HTTP](backend-http-api.md), [Data](backend-data.md), -[Security](security-auth.md), [Integrations](backend-integrations.md). +Mapping и stable codes — по [Errors](conventions/error-handling.md). +Точные ограничения — [HTTP](conventions/http-api.md), [Data](conventions/data.md), +[Security](conventions/security-auth.md), [Integrations](conventions/integrations.md). ## Сценарии и размер сервисов -Разделяй независимые операции, а не строки. `BankCardsService` отвечает за карты, -`BankOperationsService` — журнал/переводы/возвраты, `ExpenseCategoriesService` — -категории, `GetBankSummaryUseCase` — сводку. `BankCardsRepository` сохраняет -общий transaction retry и owner lookup; денежные транзакции не разрываются. -Чистые расчёты банковских карт находятся в `domain` этого модуля. +Разделяй независимые операции, а не строки. Пример: `BankCardsService` владеет +картами, `BankOperationsService` — журналом/переводами, `GetBankSummaryUseCase` — +сводкой через repository и чистый расчёт. Не разрывай денежную transaction, +общий cache/limiter или pending promises при extraction. -У инструментов `StockCatalogService` / `BondCatalogService` хранят идентичность -листинга и общий кэш slice; market service — котировки/свечи, income service — выплаты/личные позиции. -`GetStockCompanyUseCase` владеет сведениями об эмитенте. Ключи, TTL и pending -promises разделяют прежний экземпляр кэша; extract не должен менять generation. -Portfolio и operations сохраняют связную координацию снимков/страниц и кэшей. -Расчёты портфеля находятся в `portfolio/domain`; преобразование брокерских -позиций — в `tbank-portfolio.mapper.ts`. `bond-money.mapper.ts` преобразует -ответ клиента и не является value object. - -Выбирай обычную функцию для чистого вычисления; repository contract — только -для реальной границы. CQRS/buses/events, BaseRepository/BaseService, DI token -для каждого класса, mapper/presenter для каждого DTO и новые dependencies -не нужны для структурного переноса. +Расширение abstractions — по [change scope](../../.ai/conventions/code-health.md#change-scope). ## Common @@ -205,39 +229,12 @@ protobuf units/nano conversion — T-Bank integration. Domain используе ## Domain literals and magic values -Finite business vocabulary must be modeled explicitly; incidental implementation -literals stay inline. Это относится к error codes, statuses, operation/account/ -instrument/transaction types, permissions, roles, feature states и бизнес-режимам. - -Перед новым определением найди existing owner/type/value. В branching и comparisons -используй символическое значение владельца; не повторяй raw domain literals. -Храни словарь у domain/module/entity с соблюдением границ слоёв, без глобальной -свалки `constants.ts`. Сохраняй принятый pattern; для нового project-owned словаря -без подходящего аналога используй `as const` с derived union: - -```ts -const BankErrorCode = { - InvalidDate: "BANK_INVALID_DATE", - ImmutableOperation: "BANK_IMMUTABLE_OPERATION", -} as const; -type BankErrorCode = (typeof BankErrorCode)[keyof typeof BankErrorCode]; - -// Вместо code === "BANK_INVALID_DATE": -if (code === BankErrorCode.InvalidDate) { /* existing handling */ } -``` - -Значимые numeric policies (timeout, retry, page size, threshold, coefficient) -называй по смыслу и единицам: `REQUEST_TIMEOUT_MS`, `CACHE_TTL_SECONDS`, -`MAX_RETRY_COUNT`, `PAGE_SIZE`. Оставляй inline UI labels в принятом месте, -log messages, индексы, `index + 1`, `Math.max(value, 0)`, `page ?? 1`, -`percentage * 100` и очевидные локальные детали без semantic benefit. -При неясном owner/смысле оставь значение и укажи ограничение в review. - -Не мигрируй корректные existing enum/union styles массово. Не добавляй ради -правила factories, registries, schemas, loaders, abstraction layers, dependencies, -custom lint/AST rules, generation/validation scripts или тесты существования -констант. Semantic review проверяет ownership, единое написание и неизменность -wire values; обычные behavior tests проверяют поведение. +Общий выбор symbolic values и числовых policies — +[Domain values](../../.ai/conventions/code-health.md#domain-values). +Сохраняй strict TS и explicit boundary types. Для недоверенного input выбирай +`unknown` + runtime narrowing; отключённый lint `no-explicit-any` не делает +`any` нормой domain/application model. `declare` допустим для заполняемых извне +DTO fields, не для сокрытия неинициализированного owned state. OpenAPI enum metadata и runtime validation должны читать тот же vocabulary, что application/domain. Например, `BankOperationKind` принадлежит bank-cards/domain, @@ -255,14 +252,8 @@ semantic magic strings проверяются conventions/review без ново ## Logging -Единственный logger — nestjs-pino; application events используют существующий -[StructuredLogger](../../src/common/logging/structured-logger.service.ts) и -request context. Выбирай стабильное имя события и разрешённые scalar fields. -Wrapper отбрасывает неизвестные поля, но это не разрешение помещать secret -в `message`/`stack`: review sensitive logging обязателен. -Не добавляй второй logger или сырой request/body/error payload. -Источник rationale — [ADR 008](../../../docs/adr/008-pino-backend-logging.md); -при secrets/user data читай также Security. +События, levels, correlation и safe fields — по +[Observability](conventions/observability.md). ## Проверка diff --git a/.ai/conventions/backend-data.md b/.ai/conventions/data.md similarity index 90% rename from .ai/conventions/backend-data.md rename to .ai/conventions/data.md index 04954b5..83a044c 100644 --- a/.ai/conventions/backend-data.md +++ b/.ai/conventions/data.md @@ -15,13 +15,20 @@ PrismaService — существующий shared DB provider с lifecycle пр | Независимые reads | Не добавляй transaction ради внешнего вида | | Общая persistence abstraction | Только при реальной общей ответственности; generic repository для одной Prisma реализации не нужен | -Размещение repository определяется [Architecture](backend-architecture.md): +Размещение repository определяется [Architecture](../architecture.md): в слоистом module это `infrastructure/`. Repository — конкретный класс `*.repository.ts` у business owner, без обязательных interface/port/adapter. Он владеет queries, includes/filtering, DB mapping и согласованным чтением. Суффиксы `*.persistence.ts`, `*.store.ts`, `*.dao.ts` для той же роли не используем. Nest module регистрирует repository приватно. +Новые business decisions (можно ли менять архивную карту, допустим ли перевод) +принадлежат application/domain; repository сохраняет persistence и ownership +ограничения. Existing checks/HTTP helpers в BankCardsRepository — migration +candidate, не повод дублировать их при новом use case или разрывать transaction. +`BaseRepository`, `GenericRepository` и RepositoryFactory поверх Prisma +не нужны без отдельной общей ответственности. + `BankCardsRepository.summaryData` проверяет владельца карты и получает операции с категориями в одной read transaction. `GetBankSummaryUseCase` выбирает период, вызывает repository, затем чистый `bankSummary` и формирует ответ. Prisma records @@ -74,4 +81,4 @@ client обновляй из schema. Не переписывай уже прим `npx prisma validate`; после schema change `npx prisma generate` и `npm run typecheck:app`. Invariants/atomicity/migration safety требуют tests на соответствующей boundary: mock Prisma не доказывает реальную transaction. -Выбор integration/e2e и известные ограничения — в [Testing](backend-testing.md). +Выбор integration/e2e и известные ограничения — в [Testing](testing.md). diff --git a/.ai/conventions/error-handling.md b/.ai/conventions/error-handling.md new file mode 100644 index 0000000..5c417dc --- /dev/null +++ b/.ai/conventions/error-handling.md @@ -0,0 +1,40 @@ +# Backend errors + +## Поток + +```text +application/domain outcome → application mapping → HttpExceptionFilter + → application/problem+json +``` + +Сохраняй принятый профиль RFC 9457: `status`, стабильный публичный `code`, +для validation — `errors: [{ pointer, detail }]`. Точный контракт и известное +расхождение validation описаны в [HTTP API](../../../docs/architecture/http-api.md). +Не добавляй `type/title/instance` как обязательные поля без contract change. + +Чистый domain не импортирует Nest HTTP exceptions. Существующий application +service может бросить подходящий `HttpException` с public code; не нужен новый +error framework/registry. Если domain outcome требует HTTP interpretation, +маппинг принадлежит application/transport boundary. + +## Codes и безопасность + +- Используй existing code владельца, не сравнивай внутренние сообщения. + Новое значение имеет один symbolic owner; metadata/validation и consumers + согласуются по [domain values](../../../.ai/conventions/code-health.md#domain-values). +- Общий envelope, default status mapping и unexpected errors принадлежат + [HttpExceptionFilter](../../src/common/filters/http-exception.filter.ts). + Не дублируй `res.status().json()` в controllers/services. +- Неожиданный exception → `500 / INTERNAL_SERVER_ERROR`, без message/stack, + SQL, credentials или raw upstream payload в ответе. +- External auth failure и временная недоступность имеют разные outcomes; + translation выполняется на existing integration/access boundary. +- Логи используют [observability](observability.md). Redaction не + делает произвольное сообщение ошибки безопасным. + +## Проверка + +Для изменённого кода проверь observable status/code и сохранность данных. +Для validation нужен request через `configureApp`: fabricated `ValidationError` +в unit test filter не доказывает форму, выходящую из реального ValidationPipe. +OpenAPI parity проверяет schema, а не response pipeline; [Testing](testing.md). diff --git a/.ai/conventions/backend-http-api.md b/.ai/conventions/http-api.md similarity index 75% rename from .ai/conventions/backend-http-api.md rename to .ai/conventions/http-api.md index 850edcd..81e3353 100644 --- a/.ai/conventions/backend-http-api.md +++ b/.ai/conventions/http-api.md @@ -27,7 +27,7 @@ module или presentation. DTO можно передать application boundary формы; отдельный Input с теми же полями не нужен. Domain не импортирует DTO: например, чистые денежные расчёты используют `Money`, а Swagger — `MoneyDto`. Нормализованная форма input нужна только при смысловом отличии от wire contract; -выбор suffix принадлежит [Architecture](backend-architecture.md). +выбор suffix принадлежит [Architecture](../architecture.md). ValidationPipe уже настроен в `configureApp` (`transform`, `whitelist`). Не обещай rejection неизвестных fields: `forbidNonWhitelisted` не включён. Для query numbers/arrays выбирай явное преобразование и валидаторы, как в @@ -35,10 +35,9 @@ ValidationPipe уже настроен в `configureApp` (`transform`, `whitelis описывает те же required/optional/nullable/enum/range значения. Используй типизированные response DTO, `ApiOkResponse`/другой фактический status -и существующий `ApiProblemResponses`. При новых ошибках задай публичный stable -`code`; сообщение внутреннего exception не является UI contract. Не возвращай -raw upstream error, Prisma record с secrets или stack. Текущая форма ошибок -описана у HTTP owner; не добавляй поля RFC только потому, что они встречаются в tutorial. +и существующий `ApiProblemResponses`. Prisma model не является public contract: +response выбирает поля явно. Stable codes, RFC 9457 профиль и mapping принадлежат +[Errors](error-handling.md). **Безопасный путь:** используй exception и общий filter, а не локальный `res.status(...).json(...)`. Исключение — транспортная операция вроде cookie; @@ -46,6 +45,16 @@ raw upstream error, Prisma record с secrets или stack. Текущая фор ## Пример и проверка +Prefix `/api/v1` задаёт `configureApp`. Route/status/request/response/OpenAPI +change — изменение публичного backend contract; несовместимое изменение требует +согласованного перехода consumers, а не незаметной правки DTO. Новый API version +не вводи для внутреннего refactor. + +Pagination сохраняет contract endpoint: operations использует opaque cursor, +limit и DTO bounds. Не заменяй cursor offset/page и не добавляй global pagination +helper ради первого endpoint. Новая pagination явно определяет порядок, +границу страницы и end-of-list; reused cursor не должен менять filters/owner. + [Operations controller](../../src/modules/investments/operations/presentation/brokerage-operations.controller.ts) показывает guard, user.sub, DTO query и service delegation. Для нового field обновляй canonical source и generated artifacts в определённом @@ -55,4 +64,4 @@ raw upstream error, Prisma record с secrets или stack. Текущая фор errors на boundary, где они возникают. Unit вызов controller не запускает pipes, guards или serialization. `npm run test:openapi-contract` проверяет schema parity, но не поведение invalid request; выбирай соответствующий test по -[Testing](backend-testing.md). Финальный gate не заменяет semantic review ошибок. +[Testing](testing.md). Финальный gate не заменяет semantic review ошибок. diff --git a/.ai/conventions/backend-integrations.md b/.ai/conventions/integrations.md similarity index 88% rename from .ai/conventions/backend-integrations.md rename to .ai/conventions/integrations.md index 1c5d3fb..ebc2951 100644 --- a/.ai/conventions/backend-integrations.md +++ b/.ai/conventions/integrations.md @@ -2,7 +2,8 @@ ## Выбор boundary -T-Bank gRPC принадлежит TbankClient; MOEX metadata — MoexClient. Сервисы use case +T-Bank gRPC принадлежит TbankClient; MOEX metadata — MoexClient; NCR ratings — +NcrRatingsClient. Сервисы use case используют объявленные клиентом records; SDK clients, metadata и transport errors остаются внутри integration. Frontend не вызывает T-Bank и не получает credentials/proto client. Reference source — @@ -17,7 +18,7 @@ RPC, credentials/access и resilience. Эти владельцы не объед `*.client.ts` содержит transport и его retries; `*.api.ts` только перечисляет публичные exports. Все шесть `auth/operations/portfolio/tbank/moex/ncr.api.ts` имеют одинаковую роль программной границы, определённую в -[Architecture](backend-architecture.md). Логика, DTO definitions и HTTP routes +[Architecture](../architecture.md). Логика, DTO definitions и HTTP routes в эти файлы не помещаются. Type exports обозначаются `export type`. Преобразование provider records происходит на boundary: например, @@ -30,7 +31,7 @@ client или mapper для простого копирования полей. Для нового account use case сначала используй TbankAccessService: access, connection generation и invalid-token translation не дублируются в callers. Mapper внешних финансовых значений остаётся внутри business capability, -обычно в infrastructure по [Architecture](backend-architecture.md). +обычно в infrastructure по [Architecture](../architecture.md). ## Retry, cache и protocol @@ -40,6 +41,10 @@ Existing client уже владеет bounded FIFO queue, rate limit, deadline CA mechanism. Точные параметры и ограничения принадлежат [connection configuration](../../../docs/integrations/tbank.md#конфигурация-и-ограничения). +Эта queue/retry policy относится к T-Bank. MOEX/NCR HTTP clients используют +собственный bounded timeout и безопасный unavailable outcome; не приписывай им +gRPC retry policy. При изменении upstream сначала прочитай конкретный client. + При query/cache change установи key, user/account/generation ownership, freshness и поведение late response. Старый результат не может обслужить новую connection generation. Client Cached Snapshot frontend отличается от backend @@ -64,4 +69,4 @@ Generated artifacts, если есть, создавай из source, без р upstream responses. Проверь queue/deadline/retry limit, token translation, cache isolation и late response для затронутых ветвей. Fixtures синтетические; реальная сеть, секреты и пользовательские accounts не нужны. -Logging использует [existing logger](backend-architecture.md#logging). +Logging использует [existing logger](../architecture.md#logging). diff --git a/.ai/conventions/observability.md b/.ai/conventions/observability.md new file mode 100644 index 0000000..ae028cb --- /dev/null +++ b/.ai/conventions/observability.md @@ -0,0 +1,24 @@ +# Observability + +- Единственный logger — nestjs-pino. Application events используют + [StructuredLogger](../../src/common/logging/structured-logger.service.ts), + стабильное имя события и разрешённые scalar fields. `console.log` и второй + logger в production не используются. +- `info` — значимый lifecycle/outcome; `debug` — подробности успешного RPC; + `warn` — восстановимая проблема/retry; `error` — окончательный сбой. + Не добавляй log на каждый вход/выход функции или дубликат HTTP completion. +- RequestContextService и request middleware владеют correlation: серверный + request ID возвращается как `X-Request-ID`, входящий client ID хранится отдельно. + Передавай существующий context, не создавай собственный UUID в каждом service. +- Wrapper ограничивает поля, Pino redacts request secrets. Не логируй passwords, + tokens, cookie, Authorization, credentials, connection generation, raw body + или upstream error. Secret в `message/stack` не становится безопасным из-за + allowlist; правила [Security](security-auth.md) обязательны для sensitive data. +- Для ошибки используй public code и безопасный context операции/попыток; + [error mapping](error-handling.md) не отдаёт внутренние детали клиенту. + +Причины — [ADR 008](../../../docs/adr/008-pino-backend-logging.md), системное +поведение — [HTTP observability](../../../docs/architecture/http-api.md#наблюдаемость). +Проверяй новые поля/redaction/correlation existing logging tests; не добавляй +тест на каждую строку log. Existing ESLint не запрещает все вызовы `console` +отдельным правилом: до настройки встроенного `no-console` это semantic review. diff --git a/.ai/conventions/security-auth.md b/.ai/conventions/security-auth.md index 7534d4b..f4ecf59 100644 --- a/.ai/conventions/security-auth.md +++ b/.ai/conventions/security-auth.md @@ -24,6 +24,10 @@ frontend session/cache очищается и при network failure. ## Trust boundaries +Frontend не является security boundary. DTO runtime validation выполняется +server-side; whitelist не заменяет access checks. Environment/secret inputs +проверяет existing config owner; реальные credentials не попадают в fixtures. + Verified JWT не доказывает доступ к accountId из URL. Используй user.sub и existing account access policy; не доверяй userId/token из клиента как authority. Cross-user, invalid/expired/superseded token и concurrent requests — обязательные @@ -39,6 +43,11 @@ cache invalidation при замене/deletion и ограничение key ro обрабатывает существующий bcrypt flow. Не копируй требования о нормализации или длине из tutorial: сверяй runtime DTO, tests и feature source. +Предпочитай Prisma query API. Raw SQL допускается только по конкретной нужде +с параметризованными значениями; не конкатенируй untrusted input и не передавай +его в `queryRawUnsafe`/`executeRawUnsafe`. Dynamic identifiers выбирай из +закрытого server-owned набора. Проверка user ownership сохраняется и в SQL. + ## Безопасный путь и проверка При неясной ownership/crypto/session семантике прочитай exact source до edits. @@ -46,6 +55,6 @@ cache invalidation при замене/deletion и ограничение key ro фиктивным success или log. Logger allowlist/redaction не делает произвольный message безопасным. -Targeted auth/access/crypto/logging tests + [Testing](backend-testing.md). +Targeted auth/access/crypto/logging tests + [Testing](testing.md). Cookie/guard/HTTP changes требуют configured HTTP proof; frontend Bearer/retry проверяется MSW. Schema parity не доказывает отсутствие утечки secret. diff --git a/.ai/conventions/backend-testing.md b/.ai/conventions/testing.md similarity index 89% rename from .ai/conventions/backend-testing.md rename to .ai/conventions/testing.md index b86e45c..0b64f93 100644 --- a/.ai/conventions/backend-testing.md +++ b/.ai/conventions/testing.md @@ -1,5 +1,9 @@ # Backend testing +Перед нетривиальными тестами сформулируй +[общий Test Intent](../../../.ai/conventions/testing.md); отдельный документ +не требуется. Trivial low-risk изменения обходятся без него. + ## Выбор уровня Проверяй риски доступа, денежных расчётов, целостности данных, конкурентных @@ -40,8 +44,8 @@ Unit specs colocated в `src/**/*.spec.ts`; HTTP/integration — `test/`. `npm run test:e2e` запускает все HTTP suites через изолированный SQLite runner; `npm run test:sqlite` — только auth/cards. Каждая suite получает новую временную базу со штатными migrations. Прямой Jest без runner отклоняется до DB-запросов. -`prisma.config.ts` исключает влияние унаследованного `RUST_LOG=warn/error`, -вызывающего ошибку fresh migration на Prisma 7.9.1. Подробности и регрессия запуска — +`prisma.config.ts` исключает влияние унаследованного `RUST_LOG=warn/error` +на fresh migration; точная Prisma version принадлежит manifest/lockfile. Подробности и регрессия запуска — [Backend README](../../README.md#реальная-sqlite-и-сохранность-данных). HTTP suites требуют разрешения открывать локальные порты. Ошибка sandbox не считается успешным тестом; повторите проверку в подходящем окружении. diff --git a/AGENTS.md b/AGENTS.md index 73f4afb..2c50351 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,30 +1,42 @@ -# Backend +# Backend router -NestJS API; общие правила работы — в root `AGENTS.md`. -До backend-решения прочитай [router](.ai/CONVENTIONS.md) и -[sources](.ai/SOURCES.md), затем только применимые документы/разделы. +Общие инструкции — [workspace AGENTS](../AGENTS.md). Объединяй triggers задачи, +путей и обнаруженных boundaries; читай только нужные темы до решения. +Системные факты/spec/ADR выбирай в разделе [Источники](#источники) по вопросу. -## Проверка +| Решение / сигнал | Читать | +| --- | --- | +| Устройство/data flow, module/layer/public API/DI/suffix, owner | [Architecture](.ai/architecture.md) | +| TS vocabulary, domain literals, magic values | [Types/values](.ai/architecture.md#domain-literals-and-magic-values) | +| Controller/DTO/validation, route/status/response, OpenAPI | [HTTP API](.ai/conventions/http-api.md) | +| Application/domain/HTTP error, exception, code | [Errors](.ai/conventions/error-handling.md) | +| Repository/Prisma/query, transaction, migration, atomic write | [Data](.ai/conventions/data.md) | +| Tests или behavior с regression risk | [Testing](.ai/conventions/testing.md) | +| JWT/cookie/session/guard/access, encryption, secrets | [Security](.ai/conventions/security-auth.md) | +| External client/gateway/SDK/proto, retry/rate limit/cache | [Integrations](.ai/conventions/integrations.md) | +| Logging/context/levels, sensitive logs | [Observability](.ai/conventions/observability.md); sensitive data также Security | +| Изменение публичного wire contract / Swagger metadata | HTTP API + [HTTP contract source](../docs/architecture/http-api.md#источник-контракта) | +| Helper/dependency/новый pattern, refactor/scope | [Code health](../.ai/conventions/code-health.md), для пакета — [Dependencies](../.ai/conventions/dependencies.md) | +| Выбор gate, tooling, завершение | [Guardrails](../.ai/conventions/guardrails.md) | -Перед Node-командами: `nvm use --silent` по `.nvmrc`. -После lockfile install `npm ci` полный gate — `npm run verify`: -zero-warning ESLint, application/test TypeScript, Jest и build. -`lint:fix`/`format` пишут файлы и запускаются отдельно от доказательства проверки. -`test:cov` — диагностика без общего threshold; scripts принадлежат `package.json`. +Новый endpoint обычно требует HTTP API + Testing; Data/Security добавляются +по persistence/access trigger. Auth refresh активирует Security + Data + Testing. +Повтори routing по итоговому diff, включая новые файлы. Migration backlog +читается только при соответствующем finding через +[plan](../.ai/migrations/conventions-v1/plan.md). -HTTP contract changes сохраняют [направление контракта](../docs/architecture/http-api.md#источник-контракта) -и проходят contract check. Общий e2e запускается на отдельных временных DB; -применимость и маршрут проверки -описаны в [backend testing](.ai/conventions/backend-testing.md). -Generated Prisma client не редактируется вручную. Credentials, авторизация -доступа к счетам и внешние интеграции остаются server-side. +## Источники -ESLint/TypeScript не доказывают правильность domain boundaries, security или -денежных вычислений: для них нужны semantic review и risk-based tests. +Открывай только источник по возникшему вопросу. Code показывает реализацию, +docs — текущую систему, ADR — rationale, specs — requirements. Старый пример +не отменяет контракт; conflicting security/data facts выясняй до решения. -## Ветки и релизы - -Работа — в отдельной ветке от `develop`, затем PR в `develop`; в `main` напрямую -не пушить. Release/hotfix и синхронизация веток — по [root README](../README.md#релизы-и-ветки). -Commits и заголовки PR — Conventional Commits; для спецификаций допустим `spec:`. -Перед подготовкой релиза прочитай root README; версии вручную не повышать. +| Вопрос | Источник | +| --- | --- | +| HTTP errors/DTO/wire shape | [HTTP API](../docs/architecture/http-api.md); editable OpenAPI генерирует Nest | +| Auth/session, account access, secrets | [Authentication](../docs/domains/authentication/overview.md), [T-Bank](../docs/integrations/tbank.md) | +| Prisma/data ownership, хранение и доступ | [Containers](../docs/architecture/containers.md#хранимые-данные-и-границы-доступа) | +| T-Bank/cache/logging | [T-Bank](../docs/integrations/tbank.md), [Freshness](../docs/architecture/cross-cutting/data-freshness.md), [Observability](../docs/architecture/http-api.md#наблюдаемость) | +| Money, account/position/income/cache meanings | [Глоссарий](../docs/glossary.md) | +| Поведение области / требования | [Области docs](../docs/README.md#предметные-области), [Specs index](../specs/AI_INDEX.md) | +| Versions/scripts/check scope | Manifest/lockfile, ESLint/TypeScript/Jest/Prisma configs | diff --git a/README.md b/README.md index 783090f..1d5d6a3 100644 --- a/README.md +++ b/README.md @@ -78,10 +78,10 @@ runtime-валидацию. В обычном коде нужны инициал ## Документация -- [Router conventions](.ai/CONVENTIONS.md) и [источники](.ai/SOURCES.md) -- [Backend architecture](.ai/conventions/backend-architecture.md) -- [HTTP API](.ai/conventions/backend-http-api.md), [data](.ai/conventions/backend-data.md) и [testing](.ai/conventions/backend-testing.md) -- [Security/auth](.ai/conventions/security-auth.md) и [integrations](.ai/conventions/backend-integrations.md) +- [AGENTS: правила и источники](AGENTS.md) +- [Backend architecture](.ai/architecture.md) +- [HTTP API](.ai/conventions/http-api.md), [data](.ai/conventions/data.md) и [testing](.ai/conventions/testing.md) +- [Security/auth](.ai/conventions/security-auth.md) и [integrations](.ai/conventions/integrations.md) - [Карта проектных фактов](../docs/README.md) - [API contract и runtime OpenAPI](../docs/architecture/http-api.md#источник-контракта)