From 016782657acfd35ff3fd755462007938fac35156 Mon Sep 17 00:00:00 2001 From: Sergey Krylov Date: Tue, 15 Sep 2026 05:58:33 +0300 Subject: [PATCH] docs: simplify conventions layout --- .ai/CONVENTIONS.md | 9 - .ai/SOURCES.md | 19 -- .ai/architecture.md | 226 +++++++++++++++++- .ai/conventions/backend-architecture.md | 218 ----------------- .ai/conventions/{backend-data.md => data.md} | 4 +- ...nd-error-handling.md => error-handling.md} | 4 +- .../{backend-http-api.md => http-api.md} | 6 +- ...ackend-integrations.md => integrations.md} | 6 +- ...kend-observability.md => observability.md} | 2 +- .ai/conventions/security-auth.md | 2 +- .../{backend-testing.md => testing.md} | 0 AGENTS.md | 35 ++- README.md | 8 +- 13 files changed, 263 insertions(+), 276 deletions(-) delete mode 100644 .ai/CONVENTIONS.md delete mode 100644 .ai/SOURCES.md delete mode 100644 .ai/conventions/backend-architecture.md rename .ai/conventions/{backend-data.md => data.md} (98%) rename .ai/conventions/{backend-error-handling.md => error-handling.md} (95%) rename .ai/conventions/{backend-http-api.md => http-api.md} (95%) rename .ai/conventions/{backend-integrations.md => integrations.md} (95%) rename .ai/conventions/{backend-observability.md => observability.md} (94%) rename .ai/conventions/{backend-testing.md => testing.md} (100%) diff --git a/.ai/CONVENTIONS.md b/.ai/CONVENTIONS.md deleted file mode 100644 index 5791928..0000000 --- a/.ai/CONVENTIONS.md +++ /dev/null @@ -1,9 +0,0 @@ -# Backend conventions: вход - -Актуальный routing по задаче, пути и boundary находится в [AGENTS](../AGENTS.md). -Выбирай только активированные темы; при новой границе повтори routing. -[Architecture](architecture.md) объясняет mental model, [SOURCES](SOURCES.md) -ведёт к системным фактам, specifications и ADR по конкретному вопросу. - -Этот адрес сохранён для существующих входящих ссылок; второй копии правил -и таблицы маршрутов здесь нет. diff --git a/.ai/SOURCES.md b/.ai/SOURCES.md deleted file mode 100644 index 07082e0..0000000 --- a/.ai/SOURCES.md +++ /dev/null @@ -1,19 +0,0 @@ -# Источники backend - -| Что нужно установить | Владелец | -| --- | --- | -| Implementation decision и безопасный pattern | [AGENTS](../AGENTS.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/architecture.md b/.ai/architecture.md index 48724cd..a9a2311 100644 --- a/.ai/architecture.md +++ b/.ai/architecture.md @@ -1,4 +1,6 @@ -# Backend mental model +# Backend architecture + +## Устройство и data flow NestJS + TypeScript modular monolith. Composition root `AppModule` подключает бизнес-модули, validated configuration, Prisma и Pino. Общая `configureApp` @@ -19,7 +21,7 @@ HTTP controller → application service/use case → pure domain rules presentation/application/domain/infrastructure; компактные остаются плоскими. `*.module.ts` — Nest DI, `*.api.ts` — используемые cross-module exports. Application использует конкретные providers; обязательных ports на каждый -класс нет. [Module convention](conventions/backend-architecture.md). +класс нет. Stocks/bonds — slices одного instruments module: catalog владеет identity/cache, market — котировками, income — выплатами/личными позициями. Общая orchestration @@ -29,7 +31,7 @@ domain из-за extraction в функцию. Boundary mappers преобраз PrismaService владеет DB lifecycle. Самостоятельные use cases получают данные через concrete repositories; существующие связные CRUD/transaction services -сохраняют прямые queries по [data convention](conventions/backend-data.md). +сохраняют прямые queries по [data convention](conventions/data.md). Money — currency + decimal string; расчёты используют точные общие helpers. Auth управляет identity/session; TbankAccessService проверяет доступ к счёту и @@ -41,4 +43,220 @@ Runtime OpenAPI из Nest → предоставленный frontend snapshot Problem Details фильтруются общей HTTP boundary; StructuredLogger добавляет request context и ограничивает поля событий. Jest unit tests, изолированные SQLite/HTTP suites и OpenAPI parity доказывают разные части поведения. -Системные потоки и ADR выбираются через [SOURCES](SOURCES.md). +Системные потоки и ADR выбираются через [Источники](../AGENTS.md#источники). + +## Module-first organization + +Сначала выбери business owner в `modules/`, затем ответственность внутри него. + +### Progressive structure + +Три уровня организации: business module → responsibility layer → technical grouping +при реальной необходимости. Компактный module с понятными controller/DTO/service +может оставаться плоским. Ориентир 5–7 файлов не является порогом: учитывай число +ответственностей, наличие domain/repository/use cases и удобство просмотра каталога. + +Когда в module смешаны несколько services, transport, расчёты и persistence, +группируй файлы по ответственности: + +```text +feature/ +├── feature.module.ts +├── feature.api.ts # только при существующих внешних потребителях +├── presentation/ # HTTP, DTO, guards +├── application/ # сценарии, orchestration, application input +├── domain/ # чистые бизнес-правила и расчёты +└── infrastructure/ # persistence, framework и boundary mapping +``` + +Создавай только нужные слои. В корне сложного module оставляй composition и +публичную boundary; production code и colocated tests размещай в слое владельца. +Внутри business slice действует то же правило, без отдельного Nest module ради папок. +`controllers/`, `dto/`, `services/`, `use-cases/`, `persistence/`, `mappers/` +допустимы только внутри слоя, когда множество однотипных файлов мешает навигации. +Для одного controller или пары DTO дополнительная поддиректория не нужна. +Пустые папки, новые barrels, CQRS и interfaces не требуются для этой организации. + +## Поиск и создание файла + +При работе над feature проходи владельцев в порядке: + +1. Найди business module. +2. Прочитай его `*.module.ts` (для slice — composition родительского module). +3. Проверь `presentation/`: controller и transport contract. +4. Проследи вызовы в `application/`: сценарии и services. +5. Проверь используемые правила в `domain/`. +6. Проверь `infrastructure/`: repository и технические реализации. +7. По обнаруженным зависимостям переходи в `integrations/` и `common/`. + +В плоском module классифицируй файлы по suffix и следуй их ответственности. +Отсутствующий слой пропускай. Если business owner известен, начинай локально, +а не с repository-wide search. + +**Rule of locality:** code that belongs to one business capability stays inside +that capability unless it is cross-cutting. Например, расчёты карт принадлежат +bank-cards/domain, даже если реализованы обычными функциями. + +Перед созданием файла определи: + +1. Какой business module владеет изменением? Сначала owner, затем common при + подтверждённой общей потребности. +2. Какова ответственность: HTTP/DTO/guard → presentation; сценарий/orchestration → + application; чистое business rule/calculation → domain; persistence или + module-specific technical implementation → infrastructure; внешний протокол → integrations. +3. Какой approved suffix из таблицы соответствует этой ответственности? +4. Какой слой владеет файлом? В компактном плоском module сохрани размещение рядом + с owner; в слоистом используй соответствующий слой. +5. Достаточно ли colocate файл рядом с существующими? Level 3 grouping создавай + только при заметном улучшении навигации по множеству однотипных файлов. + +Если owner или роль не определены, сначала установи ответственность. Generic +`utils.ts`, `helpers.ts`, `manager.ts`, `handler.ts` и arbitrary directory её не заменяют. + +## Naming & File Suffix Convention + +Имена файлов — `kebab-case`. File suffix определяет responsibility, directory +определяет architectural layer. Размещение в таблице предпочтительно для +слоистого module; компактный плоский module сохраняет файлы в корне. +Cross-module consumers во всех строках ограничены публичной границей ниже. + +| Suffix | Когда использовать | Где | Кто зависит | Когда не использовать | +| --- | --- | --- | --- | --- | +| `*.controller.ts` | HTTP endpoint / transport | presentation | Nest wiring | DB, расчёты, SDK, сложная оркестрация | +| `*.dto.ts` | Request/response, validation, OpenAPI | presentation | Controller; application boundary при совпадении shape | Универсальная domain model | +| `*.use-case.ts` | Один самостоятельный application scenario | application | Controller, другой orchestrator | Prisma queries, Request/Response, SDK implementation | +| `*.service.ts` | Конкретная cohesive reusable capability | рядом с owner; application/domain/integration по роли | Сценарии и services; domain — только от чистого domain service | Свалка несвязанных сценариев | +| `*.repository.ts` | Persistence queries и согласованные DB reads/writes | infrastructure | Application service/use case | Domain calculations, HTTP request, external transport | +| `*.client.ts` | Низкоуровневый внешний HTTP/gRPC/SDK | integrations или infrastructure | Application orchestration, boundary mapper | Пользовательский lifecycle и domain rules | +| `*.mapper.ts` | Meaningful conversion между моделями | обычно infrastructure у преобразуемой boundary | Код соответствующей boundary | Формальное копирование нескольких одинаковых полей | +| `*.policy.ts` | Cohesive decision rule | domain/application/integration по входным данным | Владелец правила; domain — от чистой policy | Persistence, transport, generic strategy | +| `*.guard.ts` | Nest guard | presentation | Nest/controller | Основная account authorization и финансовые правила | +| `*.strategy.ts` | Framework strategy, например JWT | infrastructure | Framework wiring | Обычный service без strategy contract | +| `*.module.ts` | Nest imports/providers/controllers/exports | module root | Composition root, importing module | Бизнес-логика | +| `*.types.ts` | Малая группа связанных TS types | рядом с owner | Потребители модели на разрешённых слоях | Глобальные types всего module, runtime validation | +| `*.api.ts` | Явный список публичных exports | root конкретного module/integration | Другие modules | DTO/client/service implementation или generic API logic | +| `*-input.ts` | Application input: нормализация и проверка формы, отличающейся от wire DTO | application | Application code | Копия DTO под новым именем; вариант `*.input.ts` | +| `*.calculations.ts`, `*.rules.ts` или предметное имя | Чистые бизнес-расчёты, value semantics | domain | Application и domain | Nest/Prisma/HTTP/SDK зависимости | +| `*.spec.ts` | Существующая unit/component проверка поведения | рядом с owner/slice | Test runner | Архитектурные проверки папок и trivial wiring | + +`bank-card-input.ts` использует выбранный `*-input.ts`: переводит Money в +копейки, нормализует значения и проверяет application input. DTO не дублируется +отдельным Input type. `rub-money.ts` — domain-правила RUB; `bond-money.mapper.ts` +— преобразование ответа клиента. Эти роли не определяются словом money в имени. + +Технические framework suffixes (`*.filter.ts`, `*.middleware.ts`, +`*.decorator.ts`, `*.config.ts`, `*.validation.ts`) сохраняют соответствующие +существующие роли в common/config. `tbank.crypto.ts` — локальная реализация +шифрования токена; имя не является разрешением создавать generic crypto layer. +HTTP/e2e naming и расположение — в [Testing](conventions/testing.md). + +Без зафиксированной необходимости не вводи `*.helper.ts`, `*.utils.ts`, +`*.manager.ts`, `*.processor.ts`, `*.handler.ts`, `*.provider.ts`, `*.factory.ts`. +Новая naming convention сначала получает конкретную роль и обоснование здесь. +Для data access выбран только `*.repository.ts`, не persistence/store/dao. + +## Публичные границы + +`*.module.ts` задаёт DI boundary; `*.api.ts` перечисляет используемые другими +модулями providers, функции и типы. Внешний production import идёт через этот +API или файл Nest module. Чистый `integrations/tbank/decimal.ts` — отдельный +публичный вход для protobuf conversion: импорт арифметики не загружает DI/Prisma. +Внутри модуля используй прямые локальные импорты. +Tests могут импортировать внутренний owner для проверки поведения и mocks. +Новый export требует текущего потребителя; controller и persistence остаются +приватными. Не добавляй `index.ts` на каждом уровне. + +Circular dependencies устраняй изменением ownership/composition. `forwardRef()` +не является стандартным исправлением boundaries; исключение требует конкретной +неизбежной связи и проверки DI initialization. + +Каждый provider регистрируется у одного владельца. Потребители импортируют +его module, а не повторяют provider: это сохраняет единственный кэш и лимитер. +`AuthModule` экспортирует guard; наличие JWT не заменяет account access check. + +- `income` читает операции через `operations.api.ts`. +- `instruments` получает личные позиции через `portfolio.api.ts`. +- `tbank-connection` использует T-Bank integration и публичный сброс портфеля. +- `portfolio` и другие инвестиционные модули используют `TbankModule`, который + не зависит от business modules. Так управление подключением не образует цикл. +- `investment-calendar.ts` и `account-reference.dto.ts` разделяются только + инвестиционными областями; это не глобальная библиотека бизнес-правил. + +## Ответственность и направление зависимостей + +| Владелец | Ответственность | +| --- | --- | +| Controller / presentation | Route, verified identity, transport DTO, validation, status/cookie; вызов сценария | +| Use case / application service | Оркестрация, business validation, доступ и согласованность результата | +| Domain | Чистые правила и расчёты; без NestJS, Prisma, HTTP/Swagger и внешних SDK | +| Persistence / infrastructure | Запросы, транзакции, технические реализации границ | +| `src/integrations/{tbank,moex,ncr}` | Внешние протоколы, декодирование, retry/deadline/cache; без зависимостей от HTTP DTO business modules | +| `src/prisma` | Общий DB provider и lifecycle; не generic repository | + +Основной вызов: presentation → application → domain; application использует +конкретные infrastructure providers, зарегистрированные module. Infrastructure +может использовать application types/input и domain rules; новые interfaces +ради инверсии зависимостей не обязательны. Domain не импортирует presentation или +infrastructure. Существующие связные CRUD/transaction services могут сохранять прямые queries +по [Data](conventions/data.md); use case получает данные через repository и сам +Prisma queries не выполняет. Entity/interface на каждую таблицу не требуется. +DTO остаются транспортными runtime classes; существующее использование DTO +на application boundary допустимо по [HTTP API](conventions/http-api.md). + +Сохраняй существующий error contract: HTTP exceptions остаются на application/ +transport boundary. Чистому расчёту не нужен новый framework ошибок. +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` — журналом/переводами, `GetBankSummaryUseCase` — +сводкой через repository и чистый расчёт. Не разрывай денежную transaction, +общий cache/limiter или pending promises при extraction. + +Расширение abstractions — по [change scope](../../.ai/conventions/code-health.md#change-scope). + +## Common + +`common` содержит общий error filter, logging, API decorators, тип `Money`, +его HTTP `MoneyDto` и точную decimal-арифметику, используемые несколькими областями. Код одного +модуля принадлежит этому модулю: например, RUB/kopecks rules — банковским картам, +protobuf units/nano conversion — T-Bank integration. Domain использует +`money.types.ts`, а не Swagger DTO; это общая форма денег для карт и портфеля. +Давай функциям предметные имена; не создавай глобальные `utils.ts`/`helpers.ts`. + +## Domain literals and magic values + +Общий выбор 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, +а DTO получает допустимые значения через `Object.values(BankOperationKind)`. +Frontend переиспользует generated OpenAPI types/values по возможностям генератора, +а не определяет независимый backend-owned словарь. Внешние/generated enums +сохраняй; generated Prisma/protocol code вручную не меняй. + +`@typescript-eslint/no-magic-numbers` включён как error; базовое ESLint rule +отключено в пользу TS-версии. Исключения для очевидных чисел и их bigint форм, +индексов, default values, enum/type declarations и readonly properties заданы +в ESLint config. Test fixtures/expected values остаются inline, остальные lint +правила продолжают действовать. Numeric policy получает предметное имя и единицы; +semantic magic strings проверяются conventions/review без нового инструмента. + +## Logging + +События, levels, correlation и safe fields — по +[Observability](conventions/observability.md). + +## Проверка + +`npm run typecheck:app`, `npm run lint`, targeted Jest по изменённому use case. +Смысл boundaries проверяется review: backend не имеет отдельного автоматического +FSD/DI architecture checker. Existing tests и build не доказывают правильный owner. diff --git a/.ai/conventions/backend-architecture.md b/.ai/conventions/backend-architecture.md deleted file mode 100644 index 10734e8..0000000 --- a/.ai/conventions/backend-architecture.md +++ /dev/null @@ -1,218 +0,0 @@ -# Backend architecture - -## Module-first organization - -Устройство и data flow — в [mental model](../architecture.md). -Сначала выбери business owner в `modules/`, затем ответственность внутри него. - -### Progressive structure - -Три уровня организации: business module → responsibility layer → technical grouping -при реальной необходимости. Компактный module с понятными controller/DTO/service -может оставаться плоским. Ориентир 5–7 файлов не является порогом: учитывай число -ответственностей, наличие domain/repository/use cases и удобство просмотра каталога. - -Когда в module смешаны несколько services, transport, расчёты и persistence, -группируй файлы по ответственности: - -```text -feature/ -├── feature.module.ts -├── feature.api.ts # только при существующих внешних потребителях -├── presentation/ # HTTP, DTO, guards -├── application/ # сценарии, orchestration, application input -├── domain/ # чистые бизнес-правила и расчёты -└── infrastructure/ # persistence, framework и boundary mapping -``` - -Создавай только нужные слои. В корне сложного module оставляй composition и -публичную boundary; production code и colocated tests размещай в слое владельца. -Внутри business slice действует то же правило, без отдельного Nest module ради папок. -`controllers/`, `dto/`, `services/`, `use-cases/`, `persistence/`, `mappers/` -допустимы только внутри слоя, когда множество однотипных файлов мешает навигации. -Для одного controller или пары DTO дополнительная поддиректория не нужна. -Пустые папки, новые barrels, CQRS и interfaces не требуются для этой организации. - -## Поиск и создание файла - -При работе над feature проходи владельцев в порядке: - -1. Найди business module. -2. Прочитай его `*.module.ts` (для slice — composition родительского module). -3. Проверь `presentation/`: controller и transport contract. -4. Проследи вызовы в `application/`: сценарии и services. -5. Проверь используемые правила в `domain/`. -6. Проверь `infrastructure/`: repository и технические реализации. -7. По обнаруженным зависимостям переходи в `integrations/` и `common/`. - -В плоском module классифицируй файлы по suffix и следуй их ответственности. -Отсутствующий слой пропускай. Если business owner известен, начинай локально, -а не с repository-wide search. - -**Rule of locality:** code that belongs to one business capability stays inside -that capability unless it is cross-cutting. Например, расчёты карт принадлежат -bank-cards/domain, даже если реализованы обычными функциями. - -Перед созданием файла определи: - -1. Какой business module владеет изменением? Сначала owner, затем common при - подтверждённой общей потребности. -2. Какова ответственность: HTTP/DTO/guard → presentation; сценарий/orchestration → - application; чистое business rule/calculation → domain; persistence или - module-specific technical implementation → infrastructure; внешний протокол → integrations. -3. Какой approved suffix из таблицы соответствует этой ответственности? -4. Какой слой владеет файлом? В компактном плоском module сохрани размещение рядом - с owner; в слоистом используй соответствующий слой. -5. Достаточно ли colocate файл рядом с существующими? Level 3 grouping создавай - только при заметном улучшении навигации по множеству однотипных файлов. - -Если owner или роль не определены, сначала установи ответственность. Generic -`utils.ts`, `helpers.ts`, `manager.ts`, `handler.ts` и arbitrary directory её не заменяют. - -## Naming & File Suffix Convention - -Имена файлов — `kebab-case`. File suffix определяет responsibility, directory -определяет architectural layer. Размещение в таблице предпочтительно для -слоистого module; компактный плоский module сохраняет файлы в корне. -Cross-module consumers во всех строках ограничены публичной границей ниже. - -| Suffix | Когда использовать | Где | Кто зависит | Когда не использовать | -| --- | --- | --- | --- | --- | -| `*.controller.ts` | HTTP endpoint / transport | presentation | Nest wiring | DB, расчёты, SDK, сложная оркестрация | -| `*.dto.ts` | Request/response, validation, OpenAPI | presentation | Controller; application boundary при совпадении shape | Универсальная domain model | -| `*.use-case.ts` | Один самостоятельный application scenario | application | Controller, другой orchestrator | Prisma queries, Request/Response, SDK implementation | -| `*.service.ts` | Конкретная cohesive reusable capability | рядом с owner; application/domain/integration по роли | Сценарии и services; domain — только от чистого domain service | Свалка несвязанных сценариев | -| `*.repository.ts` | Persistence queries и согласованные DB reads/writes | infrastructure | Application service/use case | Domain calculations, HTTP request, external transport | -| `*.client.ts` | Низкоуровневый внешний HTTP/gRPC/SDK | integrations или infrastructure | Application orchestration, boundary mapper | Пользовательский lifecycle и domain rules | -| `*.mapper.ts` | Meaningful conversion между моделями | обычно infrastructure у преобразуемой boundary | Код соответствующей boundary | Формальное копирование нескольких одинаковых полей | -| `*.policy.ts` | Cohesive decision rule | domain/application/integration по входным данным | Владелец правила; domain — от чистой policy | Persistence, transport, generic strategy | -| `*.guard.ts` | Nest guard | presentation | Nest/controller | Основная account authorization и финансовые правила | -| `*.strategy.ts` | Framework strategy, например JWT | infrastructure | Framework wiring | Обычный service без strategy contract | -| `*.module.ts` | Nest imports/providers/controllers/exports | module root | Composition root, importing module | Бизнес-логика | -| `*.types.ts` | Малая группа связанных TS types | рядом с owner | Потребители модели на разрешённых слоях | Глобальные types всего module, runtime validation | -| `*.api.ts` | Явный список публичных exports | root конкретного module/integration | Другие modules | DTO/client/service implementation или generic API logic | -| `*-input.ts` | Application input: нормализация и проверка формы, отличающейся от wire DTO | application | Application code | Копия DTO под новым именем; вариант `*.input.ts` | -| `*.calculations.ts`, `*.rules.ts` или предметное имя | Чистые бизнес-расчёты, value semantics | domain | Application и domain | Nest/Prisma/HTTP/SDK зависимости | -| `*.spec.ts` | Существующая unit/component проверка поведения | рядом с owner/slice | Test runner | Архитектурные проверки папок и trivial wiring | - -`bank-card-input.ts` использует выбранный `*-input.ts`: переводит Money в -копейки, нормализует значения и проверяет application input. DTO не дублируется -отдельным Input type. `rub-money.ts` — domain-правила RUB; `bond-money.mapper.ts` -— преобразование ответа клиента. Эти роли не определяются словом money в имени. - -Технические framework suffixes (`*.filter.ts`, `*.middleware.ts`, -`*.decorator.ts`, `*.config.ts`, `*.validation.ts`) сохраняют соответствующие -существующие роли в common/config. `tbank.crypto.ts` — локальная реализация -шифрования токена; имя не является разрешением создавать generic crypto layer. -HTTP/e2e naming и расположение — в [Testing](backend-testing.md). - -Без зафиксированной необходимости не вводи `*.helper.ts`, `*.utils.ts`, -`*.manager.ts`, `*.processor.ts`, `*.handler.ts`, `*.provider.ts`, `*.factory.ts`. -Новая naming convention сначала получает конкретную роль и обоснование здесь. -Для data access выбран только `*.repository.ts`, не persistence/store/dao. - -## Публичные границы - -`*.module.ts` задаёт DI boundary; `*.api.ts` перечисляет используемые другими -модулями providers, функции и типы. Внешний production import идёт через этот -API или файл Nest module. Чистый `integrations/tbank/decimal.ts` — отдельный -публичный вход для protobuf conversion: импорт арифметики не загружает DI/Prisma. -Внутри модуля используй прямые локальные импорты. -Tests могут импортировать внутренний owner для проверки поведения и mocks. -Новый export требует текущего потребителя; controller и persistence остаются -приватными. Не добавляй `index.ts` на каждом уровне. - -Circular dependencies устраняй изменением ownership/composition. `forwardRef()` -не является стандартным исправлением boundaries; исключение требует конкретной -неизбежной связи и проверки DI initialization. - -Каждый provider регистрируется у одного владельца. Потребители импортируют -его module, а не повторяют provider: это сохраняет единственный кэш и лимитер. -`AuthModule` экспортирует guard; наличие JWT не заменяет account access check. - -- `income` читает операции через `operations.api.ts`. -- `instruments` получает личные позиции через `portfolio.api.ts`. -- `tbank-connection` использует T-Bank integration и публичный сброс портфеля. -- `portfolio` и другие инвестиционные модули используют `TbankModule`, который - не зависит от business modules. Так управление подключением не образует цикл. -- `investment-calendar.ts` и `account-reference.dto.ts` разделяются только - инвестиционными областями; это не глобальная библиотека бизнес-правил. - -## Ответственность и направление зависимостей - -| Владелец | Ответственность | -| --- | --- | -| Controller / presentation | Route, verified identity, transport DTO, validation, status/cookie; вызов сценария | -| Use case / application service | Оркестрация, business validation, доступ и согласованность результата | -| Domain | Чистые правила и расчёты; без NestJS, Prisma, HTTP/Swagger и внешних SDK | -| Persistence / infrastructure | Запросы, транзакции, технические реализации границ | -| `src/integrations/{tbank,moex,ncr}` | Внешние протоколы, декодирование, retry/deadline/cache; без зависимостей от HTTP DTO business modules | -| `src/prisma` | Общий DB provider и lifecycle; не generic repository | - -Основной вызов: presentation → application → domain; application использует -конкретные infrastructure providers, зарегистрированные module. Infrastructure -может использовать application types/input и domain rules; новые interfaces -ради инверсии зависимостей не обязательны. Domain не импортирует presentation или -infrastructure. Существующие связные CRUD/transaction services могут сохранять прямые queries -по [Data](backend-data.md); use case получает данные через repository и сам -Prisma queries не выполняет. Entity/interface на каждую таблицу не требуется. -DTO остаются транспортными runtime classes; существующее использование DTO -на application boundary допустимо по [HTTP API](backend-http-api.md). - -Сохраняй существующий error contract: HTTP exceptions остаются на application/ -transport boundary. Чистому расчёту не нужен новый framework ошибок. -Mapping и stable codes — по [Errors](backend-error-handling.md). -Точные ограничения — [HTTP](backend-http-api.md), [Data](backend-data.md), -[Security](security-auth.md), [Integrations](backend-integrations.md). - -## Сценарии и размер сервисов - -Разделяй независимые операции, а не строки. Пример: `BankCardsService` владеет -картами, `BankOperationsService` — журналом/переводами, `GetBankSummaryUseCase` — -сводкой через repository и чистый расчёт. Не разрывай денежную transaction, -общий cache/limiter или pending promises при extraction. - -Расширение abstractions — по [change scope](../../../.ai/conventions/code-health.md#change-scope). - -## Common - -`common` содержит общий error filter, logging, API decorators, тип `Money`, -его HTTP `MoneyDto` и точную decimal-арифметику, используемые несколькими областями. Код одного -модуля принадлежит этому модулю: например, RUB/kopecks rules — банковским картам, -protobuf units/nano conversion — T-Bank integration. Domain использует -`money.types.ts`, а не Swagger DTO; это общая форма денег для карт и портфеля. -Давай функциям предметные имена; не создавай глобальные `utils.ts`/`helpers.ts`. - -## Domain literals and magic values - -Общий выбор 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, -а DTO получает допустимые значения через `Object.values(BankOperationKind)`. -Frontend переиспользует generated OpenAPI types/values по возможностям генератора, -а не определяет независимый backend-owned словарь. Внешние/generated enums -сохраняй; generated Prisma/protocol code вручную не меняй. - -`@typescript-eslint/no-magic-numbers` включён как error; базовое ESLint rule -отключено в пользу TS-версии. Исключения для очевидных чисел и их bigint форм, -индексов, default values, enum/type declarations и readonly properties заданы -в ESLint config. Test fixtures/expected values остаются inline, остальные lint -правила продолжают действовать. Numeric policy получает предметное имя и единицы; -semantic magic strings проверяются conventions/review без нового инструмента. - -## Logging - -События, levels, correlation и safe fields — по -[Observability](backend-observability.md). - -## Проверка - -`npm run typecheck:app`, `npm run lint`, targeted Jest по изменённому use case. -Смысл boundaries проверяется review: backend не имеет отдельного автоматического -FSD/DI architecture checker. Existing tests и build не доказывают правильный owner. diff --git a/.ai/conventions/backend-data.md b/.ai/conventions/data.md similarity index 98% rename from .ai/conventions/backend-data.md rename to .ai/conventions/data.md index bd6cf5b..83a044c 100644 --- a/.ai/conventions/backend-data.md +++ b/.ai/conventions/data.md @@ -15,7 +15,7 @@ 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 и @@ -81,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/backend-error-handling.md b/.ai/conventions/error-handling.md similarity index 95% rename from .ai/conventions/backend-error-handling.md rename to .ai/conventions/error-handling.md index 7072d38..5c417dc 100644 --- a/.ai/conventions/backend-error-handling.md +++ b/.ai/conventions/error-handling.md @@ -29,7 +29,7 @@ error framework/registry. Если domain outcome требует HTTP interpreta SQL, credentials или raw upstream payload в ответе. - External auth failure и временная недоступность имеют разные outcomes; translation выполняется на existing integration/access boundary. -- Логи используют [observability](backend-observability.md). Redaction не +- Логи используют [observability](observability.md). Redaction не делает произвольное сообщение ошибки безопасным. ## Проверка @@ -37,4 +37,4 @@ error framework/registry. Если domain outcome требует HTTP interpreta Для изменённого кода проверь observable status/code и сохранность данных. Для validation нужен request через `configureApp`: fabricated `ValidationError` в unit test filter не доказывает форму, выходящую из реального ValidationPipe. -OpenAPI parity проверяет schema, а не response pipeline; [Testing](backend-testing.md). +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 95% rename from .ai/conventions/backend-http-api.md rename to .ai/conventions/http-api.md index fcb1022..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 выбирай явное преобразование и валидаторы, как в @@ -37,7 +37,7 @@ ValidationPipe уже настроен в `configureApp` (`transform`, `whitelis Используй типизированные response DTO, `ApiOkResponse`/другой фактический status и существующий `ApiProblemResponses`. Prisma model не является public contract: response выбирает поля явно. Stable codes, RFC 9457 профиль и mapping принадлежат -[Errors](backend-error-handling.md). +[Errors](error-handling.md). **Безопасный путь:** используй exception и общий filter, а не локальный `res.status(...).json(...)`. Исключение — транспортная операция вроде cookie; @@ -64,4 +64,4 @@ helper ради первого endpoint. Новая pagination явно опре 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 95% rename from .ai/conventions/backend-integrations.md rename to .ai/conventions/integrations.md index 9ade729..ebc2951 100644 --- a/.ai/conventions/backend-integrations.md +++ b/.ai/conventions/integrations.md @@ -18,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: например, @@ -31,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 @@ -69,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/backend-observability.md b/.ai/conventions/observability.md similarity index 94% rename from .ai/conventions/backend-observability.md rename to .ai/conventions/observability.md index b3ff97c..ae028cb 100644 --- a/.ai/conventions/backend-observability.md +++ b/.ai/conventions/observability.md @@ -15,7 +15,7 @@ или upstream error. Secret в `message/stack` не становится безопасным из-за allowlist; правила [Security](security-auth.md) обязательны для sensitive data. - Для ошибки используй public code и безопасный context операции/попыток; - [error mapping](backend-error-handling.md) не отдаёт внутренние детали клиенту. + [error mapping](error-handling.md) не отдаёт внутренние детали клиенту. Причины — [ADR 008](../../../docs/adr/008-pino-backend-logging.md), системное поведение — [HTTP observability](../../../docs/architecture/http-api.md#наблюдаемость). diff --git a/.ai/conventions/security-auth.md b/.ai/conventions/security-auth.md index eafe2a9..f4ecf59 100644 --- a/.ai/conventions/security-auth.md +++ b/.ai/conventions/security-auth.md @@ -55,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 100% rename from .ai/conventions/backend-testing.md rename to .ai/conventions/testing.md diff --git a/AGENTS.md b/AGENTS.md index e35f322..2c50351 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,20 +2,19 @@ Общие инструкции — [workspace AGENTS](../AGENTS.md). Объединяй triggers задачи, путей и обнаруженных boundaries; читай только нужные темы до решения. -[Sources](.ai/SOURCES.md) выбирает факты/spec/ADR по возникшему вопросу. +Системные факты/spec/ADR выбирай в разделе [Источники](#источники) по вопросу. | Решение / сигнал | Читать | | --- | --- | -| Первое знакомство, data flow, общая архитектура | [Mental model](.ai/architecture.md) | -| Module/layer/public API/DI/suffix, owner, перенос responsibility | [Modules/application](.ai/conventions/backend-architecture.md) | -| TS vocabulary, domain literals, magic values | [Types/values](.ai/conventions/backend-architecture.md#domain-literals-and-magic-values) | -| Controller/DTO/validation, route/status/response, OpenAPI | [HTTP API](.ai/conventions/backend-http-api.md) | -| Application/domain/HTTP error, exception, code | [Errors](.ai/conventions/backend-error-handling.md) | -| Repository/Prisma/query, transaction, migration, atomic write | [Data](.ai/conventions/backend-data.md) | -| Tests или behavior с regression risk | [Testing](.ai/conventions/backend-testing.md) | +| Устройство/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/backend-integrations.md) | -| Logging/context/levels, sensitive logs | [Observability](.ai/conventions/backend-observability.md); sensitive data также Security | +| 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) | @@ -25,3 +24,19 @@ Повтори routing по итоговому diff, включая новые файлы. Migration backlog читается только при соответствующем finding через [plan](../.ai/migrations/conventions-v1/plan.md). + +## Источники + +Открывай только источник по возникшему вопросу. Code показывает реализацию, +docs — текущую систему, ADR — rationale, specs — requirements. Старый пример +не отменяет контракт; conflicting security/data facts выясняй до решения. + +| Вопрос | Источник | +| --- | --- | +| 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#источник-контракта)