docs: refine backend conventions and routing #18

Merged
ksv741 merged 2 commits from codex/engineering-conventions into develop 2026-09-15 06:13:32 +03:00
12 changed files with 222 additions and 165 deletions
-25
View File
@@ -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 файлы.
-19
View File
@@ -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 не требуется.
@@ -1,12 +1,53 @@
# Backend architecture # 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 ## Module-first organization
Backend — domain-oriented modular monolith с лёгким разделением Сначала выбери business owner в `modules/`, затем ответственность внутри него.
по ответственности. `src/modules` группирует код по бизнес-возможности: `auth`, `banking/bank-cards`, области `investments`.
В инвестициях отдельные Nest modules владеют `brokerage-accounts`, `portfolio`,
`operations`, `income`, `upcoming-events`, `instruments`, `tbank-connection`.
`instruments/stocks` и `instruments/bonds` — slices внутри одного модуля.
### Progressive structure ### Progressive structure
@@ -36,15 +77,6 @@ feature/
Для одного controller или пары DTO дополнительная поддиректория не нужна. Для одного controller или пары DTO дополнительная поддиректория не нужна.
Пустые папки, новые barrels, CQRS и interfaces не требуются для этой организации. Пустые папки, новые 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 проходи владельцев в порядке: При работе над feature проходи владельцев в порядке:
@@ -116,7 +148,7 @@ Cross-module consumers во всех строках ограничены пуб
`*.decorator.ts`, `*.config.ts`, `*.validation.ts`) сохраняют соответствующие `*.decorator.ts`, `*.config.ts`, `*.validation.ts`) сохраняют соответствующие
существующие роли в common/config. `tbank.crypto.ts` — локальная реализация существующие роли в common/config. `tbank.crypto.ts` — локальная реализация
шифрования токена; имя не является разрешением создавать generic crypto layer. шифрования токена; имя не является разрешением создавать generic crypto layer.
HTTP/e2e naming и расположение — в [Testing](backend-testing.md). HTTP/e2e naming и расположение — в [Testing](conventions/testing.md).
Без зафиксированной необходимости не вводи `*.helper.ts`, `*.utils.ts`, Без зафиксированной необходимости не вводи `*.helper.ts`, `*.utils.ts`,
`*.manager.ts`, `*.processor.ts`, `*.handler.ts`, `*.provider.ts`, `*.factory.ts`. `*.manager.ts`, `*.processor.ts`, `*.handler.ts`, `*.provider.ts`, `*.factory.ts`.
@@ -134,6 +166,10 @@ Tests могут импортировать внутренний owner для п
Новый export требует текущего потребителя; controller и persistence остаются Новый export требует текущего потребителя; controller и persistence остаются
приватными. Не добавляй `index.ts` на каждом уровне. приватными. Не добавляй `index.ts` на каждом уровне.
Circular dependencies устраняй изменением ownership/composition. `forwardRef()`
не является стандартным исправлением boundaries; исключение требует конкретной
неизбежной связи и проверки DI initialization.
Каждый provider регистрируется у одного владельца. Потребители импортируют Каждый provider регистрируется у одного владельца. Потребители импортируют
его module, а не повторяют provider: это сохраняет единственный кэш и лимитер. его module, а не повторяют provider: это сохраняет единственный кэш и лимитер.
`AuthModule` экспортирует guard; наличие JWT не заменяет account access check. `AuthModule` экспортирует guard; наличие JWT не заменяет account access check.
@@ -162,37 +198,25 @@ Tests могут импортировать внутренний owner для п
может использовать application types/input и domain rules; новые interfaces может использовать application types/input и domain rules; новые interfaces
ради инверсии зависимостей не обязательны. Domain не импортирует presentation или ради инверсии зависимостей не обязательны. Domain не импортирует presentation или
infrastructure. Существующие связные CRUD/transaction services могут сохранять прямые queries infrastructure. Существующие связные CRUD/transaction services могут сохранять прямые queries
по [Data](backend-data.md); use case получает данные через repository и сам по [Data](conventions/data.md); use case получает данные через repository и сам
Prisma queries не выполняет. Entity/interface на каждую таблицу не требуется. Prisma queries не выполняет. Entity/interface на каждую таблицу не требуется.
DTO остаются транспортными runtime classes; существующее использование DTO 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/ Сохраняй существующий error contract: HTTP exceptions остаются на application/
transport boundary. Чистому расчёту не нужен новый framework ошибок. transport boundary. Чистому расчёту не нужен новый framework ошибок.
Точные ограничения — [HTTP](backend-http-api.md), [Data](backend-data.md), Mapping и stable codes — по [Errors](conventions/error-handling.md).
[Security](security-auth.md), [Integrations](backend-integrations.md). Точные ограничения — [HTTP](conventions/http-api.md), [Data](conventions/data.md),
[Security](conventions/security-auth.md), [Integrations](conventions/integrations.md).
## Сценарии и размер сервисов ## Сценарии и размер сервисов
Разделяй независимые операции, а не строки. `BankCardsService` отвечает за карты, Разделяй независимые операции, а не строки. Пример: `BankCardsService` владеет
`BankOperationsService` — журнал/переводы/возвраты, `ExpenseCategoriesService` — картами, `BankOperationsService` — журналом/переводами, `GetBankSummaryUseCase` —
категории, `GetBankSummaryUseCase` — сводку. `BankCardsRepository` сохраняет сводкой через repository и чистый расчёт. Не разрывай денежную transaction,
общий transaction retry и owner lookup; денежные транзакции не разрываются. общий cache/limiter или pending promises при extraction.
Чистые расчёты банковских карт находятся в `domain` этого модуля.
У инструментов `StockCatalogService` / `BondCatalogService` хранят идентичность Расширение abstractions — по [change scope](../../.ai/conventions/code-health.md#change-scope).
листинга и общий кэш 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
не нужны для структурного переноса.
## Common ## Common
@@ -205,39 +229,12 @@ protobuf units/nano conversion — T-Bank integration. Domain используе
## Domain literals and magic values ## Domain literals and magic values
Finite business vocabulary must be modeled explicitly; incidental implementation Общий выбор symbolic values и числовых policies —
literals stay inline. Это относится к error codes, statuses, operation/account/ [Domain values](../../.ai/conventions/code-health.md#domain-values).
instrument/transaction types, permissions, roles, feature states и бизнес-режимам. Сохраняй strict TS и explicit boundary types. Для недоверенного input выбирай
`unknown` + runtime narrowing; отключённый lint `no-explicit-any` не делает
Перед новым определением найди existing owner/type/value. В branching и comparisons `any` нормой domain/application model. `declare` допустим для заполняемых извне
используй символическое значение владельца; не повторяй raw domain literals. DTO fields, не для сокрытия неинициализированного owned state.
Храни словарь у 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 проверяют поведение.
OpenAPI enum metadata и runtime validation должны читать тот же vocabulary, OpenAPI enum metadata и runtime validation должны читать тот же vocabulary,
что application/domain. Например, `BankOperationKind` принадлежит bank-cards/domain, что application/domain. Например, `BankOperationKind` принадлежит bank-cards/domain,
@@ -255,14 +252,8 @@ semantic magic strings проверяются conventions/review без ново
## Logging ## Logging
Единственный logger — nestjs-pino; application events используют существующий События, levels, correlation и safe fields — по
[StructuredLogger](../../src/common/logging/structured-logger.service.ts) и [Observability](conventions/observability.md).
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.
## Проверка ## Проверка
@@ -15,13 +15,20 @@ PrismaService — существующий shared DB provider с lifecycle пр
| Независимые reads | Не добавляй transaction ради внешнего вида | | Независимые reads | Не добавляй transaction ради внешнего вида |
| Общая persistence abstraction | Только при реальной общей ответственности; generic repository для одной Prisma реализации не нужен | | Общая persistence abstraction | Только при реальной общей ответственности; generic repository для одной Prisma реализации не нужен |
Размещение repository определяется [Architecture](backend-architecture.md): Размещение repository определяется [Architecture](../architecture.md):
в слоистом module это `infrastructure/`. в слоистом module это `infrastructure/`.
Repository — конкретный класс `*.repository.ts` у business owner, без обязательных Repository — конкретный класс `*.repository.ts` у business owner, без обязательных
interface/port/adapter. Он владеет queries, includes/filtering, DB mapping и interface/port/adapter. Он владеет queries, includes/filtering, DB mapping и
согласованным чтением. Суффиксы `*.persistence.ts`, `*.store.ts`, `*.dao.ts` согласованным чтением. Суффиксы `*.persistence.ts`, `*.store.ts`, `*.dao.ts`
для той же роли не используем. Nest module регистрирует repository приватно. для той же роли не используем. Nest module регистрирует repository приватно.
Новые business decisions (можно ли менять архивную карту, допустим ли перевод)
принадлежат application/domain; repository сохраняет persistence и ownership
ограничения. Existing checks/HTTP helpers в BankCardsRepository — migration
candidate, не повод дублировать их при новом use case или разрывать transaction.
`BaseRepository<T>`, `GenericRepository<T>` и RepositoryFactory поверх Prisma
не нужны без отдельной общей ответственности.
`BankCardsRepository.summaryData` проверяет владельца карты и получает операции `BankCardsRepository.summaryData` проверяет владельца карты и получает операции
с категориями в одной read transaction. `GetBankSummaryUseCase` выбирает период, с категориями в одной read transaction. `GetBankSummaryUseCase` выбирает период,
вызывает repository, затем чистый `bankSummary` и формирует ответ. Prisma records вызывает repository, затем чистый `bankSummary` и формирует ответ. Prisma records
@@ -74,4 +81,4 @@ client обновляй из schema. Не переписывай уже прим
`npx prisma validate`; после schema change `npx prisma generate` и `npx prisma validate`; после schema change `npx prisma generate` и
`npm run typecheck:app`. Invariants/atomicity/migration safety требуют tests `npm run typecheck:app`. Invariants/atomicity/migration safety требуют tests
на соответствующей boundary: mock Prisma не доказывает реальную transaction. на соответствующей boundary: mock Prisma не доказывает реальную transaction.
Выбор integration/e2e и известные ограничения — в [Testing](backend-testing.md). Выбор integration/e2e и известные ограничения — в [Testing](testing.md).
+40
View File
@@ -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).
@@ -27,7 +27,7 @@ module или presentation. DTO можно передать application boundary
формы; отдельный Input с теми же полями не нужен. Domain не импортирует DTO: формы; отдельный Input с теми же полями не нужен. Domain не импортирует DTO:
например, чистые денежные расчёты используют `Money`, а Swagger — `MoneyDto`. например, чистые денежные расчёты используют `Money`, а Swagger — `MoneyDto`.
Нормализованная форма input нужна только при смысловом отличии от wire contract; Нормализованная форма input нужна только при смысловом отличии от wire contract;
выбор suffix принадлежит [Architecture](backend-architecture.md). выбор suffix принадлежит [Architecture](../architecture.md).
ValidationPipe уже настроен в `configureApp` (`transform`, `whitelist`). ValidationPipe уже настроен в `configureApp` (`transform`, `whitelist`).
Не обещай rejection неизвестных fields: `forbidNonWhitelisted` не включён. Не обещай rejection неизвестных fields: `forbidNonWhitelisted` не включён.
Для query numbers/arrays выбирай явное преобразование и валидаторы, как в Для query numbers/arrays выбирай явное преобразование и валидаторы, как в
@@ -35,10 +35,9 @@ ValidationPipe уже настроен в `configureApp` (`transform`, `whitelis
описывает те же required/optional/nullable/enum/range значения. описывает те же required/optional/nullable/enum/range значения.
Используй типизированные response DTO, `ApiOkResponse`/другой фактический status Используй типизированные response DTO, `ApiOkResponse`/другой фактический status
и существующий `ApiProblemResponses`. При новых ошибках задай публичный stable и существующий `ApiProblemResponses`. Prisma model не является public contract:
`code`; сообщение внутреннего exception не является UI contract. Не возвращай response выбирает поля явно. Stable codes, RFC 9457 профиль и mapping принадлежат
raw upstream error, Prisma record с secrets или stack. Текущая форма ошибок [Errors](error-handling.md).
описана у HTTP owner; не добавляй поля RFC только потому, что они встречаются в tutorial.
**Безопасный путь:** используй exception и общий filter, а не локальный **Безопасный путь:** используй exception и общий filter, а не локальный
`res.status(...).json(...)`. Исключение — транспортная операция вроде cookie; `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) [Operations controller](../../src/modules/investments/operations/presentation/brokerage-operations.controller.ts)
показывает guard, user.sub, DTO query и service delegation. показывает guard, user.sub, DTO query и service delegation.
Для нового field обновляй canonical source и generated artifacts в определённом Для нового field обновляй canonical source и generated artifacts в определённом
@@ -55,4 +64,4 @@ raw upstream error, Prisma record с secrets или stack. Текущая фор
errors на boundary, где они возникают. Unit вызов controller не запускает pipes, errors на boundary, где они возникают. Unit вызов controller не запускает pipes,
guards или serialization. `npm run test:openapi-contract` проверяет schema parity, guards или serialization. `npm run test:openapi-contract` проверяет schema parity,
но не поведение invalid request; выбирай соответствующий test по но не поведение invalid request; выбирай соответствующий test по
[Testing](backend-testing.md). Финальный gate не заменяет semantic review ошибок. [Testing](testing.md). Финальный gate не заменяет semantic review ошибок.
@@ -2,7 +2,8 @@
## Выбор boundary ## Выбор 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 используют объявленные клиентом records; SDK clients, metadata и transport errors
остаются внутри integration. Frontend не вызывает остаются внутри integration. Frontend не вызывает
T-Bank и не получает credentials/proto client. Reference source — T-Bank и не получает credentials/proto client. Reference source —
@@ -17,7 +18,7 @@ RPC, credentials/access и resilience. Эти владельцы не объед
`*.client.ts` содержит transport и его retries; `*.api.ts` только перечисляет `*.client.ts` содержит transport и его retries; `*.api.ts` только перечисляет
публичные exports. Все шесть `auth/operations/portfolio/tbank/moex/ncr.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`. в эти файлы не помещаются. Type exports обозначаются `export type`.
Преобразование provider records происходит на boundary: например, Преобразование provider records происходит на boundary: например,
@@ -30,7 +31,7 @@ client или mapper для простого копирования полей.
Для нового account use case сначала используй TbankAccessService: access, Для нового account use case сначала используй TbankAccessService: access,
connection generation и invalid-token translation не дублируются в callers. connection generation и invalid-token translation не дублируются в callers.
Mapper внешних финансовых значений остаётся внутри business capability, Mapper внешних финансовых значений остаётся внутри business capability,
обычно в infrastructure по [Architecture](backend-architecture.md). обычно в infrastructure по [Architecture](../architecture.md).
## Retry, cache и protocol ## Retry, cache и protocol
@@ -40,6 +41,10 @@ Existing client уже владеет bounded FIFO queue, rate limit, deadline
CA mechanism. Точные параметры и ограничения принадлежат CA mechanism. Точные параметры и ограничения принадлежат
[connection configuration](../../../docs/integrations/tbank.md#конфигурация-и-ограничения). [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, При query/cache change установи key, user/account/generation ownership,
freshness и поведение late response. Старый результат не может обслужить новую freshness и поведение late response. Старый результат не может обслужить новую
connection generation. Client Cached Snapshot frontend отличается от backend connection generation. Client Cached Snapshot frontend отличается от backend
@@ -64,4 +69,4 @@ Generated artifacts, если есть, создавай из source, без р
upstream responses. Проверь queue/deadline/retry limit, token translation, upstream responses. Проверь queue/deadline/retry limit, token translation,
cache isolation и late response для затронутых ветвей. Fixtures синтетические; cache isolation и late response для затронутых ветвей. Fixtures синтетические;
реальная сеть, секреты и пользовательские accounts не нужны. реальная сеть, секреты и пользовательские accounts не нужны.
Logging использует [existing logger](backend-architecture.md#logging). Logging использует [existing logger](../architecture.md#logging).
+24
View File
@@ -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.
+10 -1
View File
@@ -24,6 +24,10 @@ frontend session/cache очищается и при network failure.
## Trust boundaries ## 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 и Verified JWT не доказывает доступ к accountId из URL. Используй user.sub и
existing account access policy; не доверяй userId/token из клиента как authority. existing account access policy; не доверяй userId/token из клиента как authority.
Cross-user, invalid/expired/superseded token и concurrent requests — обязательные Cross-user, invalid/expired/superseded token и concurrent requests — обязательные
@@ -39,6 +43,11 @@ cache invalidation при замене/deletion и ограничение key ro
обрабатывает существующий bcrypt flow. Не копируй требования о нормализации обрабатывает существующий bcrypt flow. Не копируй требования о нормализации
или длине из tutorial: сверяй runtime DTO, tests и feature source. или длине из 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. При неясной ownership/crypto/session семантике прочитай exact source до edits.
@@ -46,6 +55,6 @@ cache invalidation при замене/deletion и ограничение key ro
фиктивным success или log. Logger allowlist/redaction не делает произвольный фиктивным success или log. Logger allowlist/redaction не делает произвольный
message безопасным. 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 Cookie/guard/HTTP changes требуют configured HTTP proof; frontend Bearer/retry
проверяется MSW. Schema parity не доказывает отсутствие утечки secret. проверяется MSW. Schema parity не доказывает отсутствие утечки secret.
@@ -1,5 +1,9 @@
# Backend testing # 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:e2e` запускает все HTTP suites через изолированный SQLite runner;
`npm run test:sqlite` — только auth/cards. Каждая suite получает новую временную `npm run test:sqlite` — только auth/cards. Каждая suite получает новую временную
базу со штатными migrations. Прямой Jest без runner отклоняется до DB-запросов. базу со штатными migrations. Прямой Jest без runner отклоняется до DB-запросов.
`prisma.config.ts` исключает влияние унаследованного `RUST_LOG=warn/error`, `prisma.config.ts` исключает влияние унаследованного `RUST_LOG=warn/error`
вызывающего ошибку fresh migration на Prisma 7.9.1. Подробности и регрессия запуска — на fresh migration; точная Prisma version принадлежит manifest/lockfile. Подробности и регрессия запуска —
[Backend README](../../README.md#реальная-sqlite-и-сохранность-данных). [Backend README](../../README.md#реальная-sqlite-и-сохранность-данных).
HTTP suites требуют разрешения открывать локальные порты. Ошибка sandbox HTTP suites требуют разрешения открывать локальные порты. Ошибка sandbox
не считается успешным тестом; повторите проверку в подходящем окружении. не считается успешным тестом; повторите проверку в подходящем окружении.
+36 -24
View File
@@ -1,30 +1,42 @@
# Backend # Backend router
NestJS API; общие правила работы — в root `AGENTS.md`. Общие инструкции — [workspace AGENTS](../AGENTS.md). Объединяй triggers задачи,
До backend-решения прочитай [router](.ai/CONVENTIONS.md) и путей и обнаруженных boundaries; читай только нужные темы до решения.
[sources](.ai/SOURCES.md), затем только применимые документы/разделы. Системные факты/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`. Новый endpoint обычно требует HTTP API + Testing; Data/Security добавляются
После lockfile install `npm ci` полный gate — `npm run verify`: по persistence/access trigger. Auth refresh активирует Security + Data + Testing.
zero-warning ESLint, application/test TypeScript, Jest и build. Повтори routing по итоговому diff, включая новые файлы. Migration backlog
`lint:fix`/`format` пишут файлы и запускаются отдельно от доказательства проверки. читается только при соответствующем finding через
`test:cov` — диагностика без общего threshold; scripts принадлежат `package.json`. [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 или Открывай только источник по возникшему вопросу. Code показывает реализацию,
денежных вычислений: для них нужны semantic review и risk-based tests. docs — текущую систему, ADR — rationale, specs — requirements. Старый пример
не отменяет контракт; conflicting security/data facts выясняй до решения.
## Ветки и релизы | Вопрос | Источник |
| --- | --- |
Работа — в отдельной ветке от `develop`, затем PR в `develop`; в `main` напрямую | HTTP errors/DTO/wire shape | [HTTP API](../docs/architecture/http-api.md); editable OpenAPI генерирует Nest |
не пушить. Release/hotfix и синхронизация веток — по [root README](../README.md#релизы-и-ветки). | Auth/session, account access, secrets | [Authentication](../docs/domains/authentication/overview.md), [T-Bank](../docs/integrations/tbank.md) |
Commits и заголовки PR — Conventional Commits; для спецификаций допустим `spec:`. | Prisma/data ownership, хранение и доступ | [Containers](../docs/architecture/containers.md#хранимые-данные-и-границы-доступа) |
Перед подготовкой релиза прочитай root README; версии вручную не повышать. | 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 |
+4 -4
View File
@@ -78,10 +78,10 @@ runtime-валидацию. В обычном коде нужны инициал
## Документация ## Документация
- [Router conventions](.ai/CONVENTIONS.md) и [источники](.ai/SOURCES.md) - [AGENTS: правила и источники](AGENTS.md)
- [Backend architecture](.ai/conventions/backend-architecture.md) - [Backend architecture](.ai/architecture.md)
- [HTTP API](.ai/conventions/backend-http-api.md), [data](.ai/conventions/backend-data.md) и [testing](.ai/conventions/backend-testing.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/backend-integrations.md) - [Security/auth](.ai/conventions/security-auth.md) и [integrations](.ai/conventions/integrations.md)
- [Карта проектных фактов](../docs/README.md) - [Карта проектных фактов](../docs/README.md)
- [API contract и runtime OpenAPI](../docs/architecture/http-api.md#источник-контракта) - [API contract и runtime OpenAPI](../docs/architecture/http-api.md#источник-контракта)