This commit is contained in:
2026-07-30 20:48:05 +03:00
parent d3b37eb4bd
commit 4ce6bae61c
23 changed files with 340 additions and 1694 deletions

View File

@@ -5,8 +5,8 @@
```text
domains/auth/
├── business/
├── presets/
├── adapters/
├── presets/ # Обязательная Group
├── adapters/ # При наличии technical dependencies
└── react/
├── session/
└── login-form/
@@ -15,9 +15,9 @@ domains/auth/
## Основные границы
- [Доменный пакет](./domain-package.md) определяет предметную и структурную границу.
- [Business](./business.md) владеет `DomainApi`, фабрикой и доменными ошибками.
- [Фабрика, зависимости и adapters](./factory-ports-adapters.md) отделяют business от технической реализации.
- [Presets](./presets.md) собирают один API для нужных окружений.
- [Business](./business.md) владеет `DomainApi` и разделяет public types, factory и error runtime по трём фасетам.
- [Фабрика, зависимости и adapters](./factory-ports-adapters.md) требуют отдельный SLM-модуль для каждой production adapter implementation.
- [Presets](./presets.md) обязательны и собирают один API для нужных окружений.
- [Framework Groups](./framework-bindings.md) содержат domain-specific SLM-модули фреймворка.
- [Тестирование](./testing.md) проверяет каждого владельца через его публичную границу.
- [Миграция auth](./auth-example.md) показывает переход с Level 1.

View File

@@ -5,6 +5,10 @@
## Связанное правило
- [`SLM-L2-MIGRATION-A017`](../../rules/level-2.md#slm-l2-migration-a017)
- [`SLM-L2-BUSINESS-A019`](../../rules/level-2.md#slm-l2-business-a019)
- [`SLM-L2-PRESET-A020`](../../rules/level-2.md#slm-l2-preset-a020)
- [`SLM-L2-ADAPTER-R021`](../../rules/level-2.md#slm-l2-adapter-r021)
- [`SLM-L2-BUSINESS-A022`](../../rules/level-2.md#slm-l2-business-a022)
## Исходная форма Level 1
@@ -29,10 +33,18 @@ domains/auth/ # Доменный пакет
│ ├── lib/
│ ├── services/
│ ├── types/
── index.ts
├── presets/ # Group
── index.ts # Только public types
│ ├── factory.ts # Public factory entry
│ └── error.ts # Public error runtime entry
├── adapters/ # Group
│ ├── phone-http/ # SLM-модуль
│ │ └── index.ts
│ ├── browser-session/ # SLM-модуль
│ │ └── index.ts
│ └── request-session/ # SLM-модуль
│ └── index.ts
├── presets/ # Обязательная Group
│ ├── browser/ # SLM-модуль
│ │ ├── adapters/
│ │ └── index.ts
│ └── request/ # SLM-модуль
│ └── index.ts
@@ -47,20 +59,35 @@ domains/auth/ # Доменный пакет
## Перенос ответственности
| Исходная часть | Владелец Level 2 |
|---|---|
| Сценарии, предметные типы, единый API | `auth/business` |
| Коды, тип и guard ошибок | `auth/business` |
| Browser storage и HTTP adapters | `auth/presets/browser` |
| Cookies, request data и server adapters | `auth/presets/request` |
| Provider и session hooks | `auth/react/session` |
| Переиспользуемая форма | `auth/react/login-form` |
| Страница, текст и redirect | `compositions` |
| Исходная часть | Владелец Level 2 | Публичный путь |
|---|---|---|
| Сценарии и public types | `auth/business` | `auth/business` |
| Runtime-фабрика | `auth/business` | `auth/business/factory` |
| Коды и guards ошибок | `auth/business` | `auth/business/error` |
| Browser storage и HTTP adapters | Соответствующий adapter-модуль | `auth/adapters/*` |
| Cookies, request data и server adapters | Соответствующий adapter-модуль | `auth/adapters/*` |
| Выбор browser implementations | `auth/presets/browser` | `auth/presets/browser` |
| Выбор request implementations | `auth/presets/request` | `auth/presets/request` |
| Provider и session hooks | `auth/react/session` | `auth/react/session` |
| Переиспользуемая форма | `auth/react/login-form` | `auth/react/login-form` |
| Страница, текст и redirect | `compositions` | API конкретной composition |
## Новые импорты
```ts
import { authFactory, isAuthError } from '@/domains/auth/business'
import type {
AuthApi,
AuthError,
AuthErrorCode,
} from '@/domains/auth/business'
import { authFactory } from '@/domains/auth/business/factory'
import {
AUTH_ERROR_CODES,
isAuthError,
} from '@/domains/auth/business/error'
import { createPhoneHttpAdapter } from '@/domains/auth/adapters/phone-http'
import { createBrowserAuth } from '@/domains/auth/presets/browser'
import { AuthSessionProvider } from '@/domains/auth/react/session'
import { LoginForm } from '@/domains/auth/react/login-form'
@@ -90,12 +117,13 @@ User не импортирует runtime-код Auth, а его React-модул
## Порядок перехода
1. Определить dependency-connected набор доменов, который нужно мигрировать вместе.
2. Выделить `business` и одну фабрику без environment-specific import-графа.
2. Выделить `business` и три публичных фасета: type-only barrel, `factory` и `error`.
3. Зафиксировать `DomainApi`, error codes, error type и runtime guard.
4. Перенести browser/server wiring в нужные presets и adapters.
5. Разделить React-ответственности на модули внутри Group `react`.
6. Перенести страницы, redirects и multi-domain UI в `compositions`.
7. Перевести внешние импорты на module-specific paths.
8. Удалить старый root `index.ts` и проверить import-граф.
4. Оформить каждую production implementation отдельным модулем `adapters/*`.
5. Создать минимум один preset и перенести туда повторяемый выбор adapter-модулей.
6. Разделить React-ответственности на модули внутри Group `react`.
7. Перенести страницы, redirects и multi-domain UI в `compositions`.
8. Перевести внешние импорты на разрешённые public paths.
9. Удалить старый root `index.ts` и проверить import-граф.
Простые доменные модули могут оставаться в SLM root только как временное миграционное состояние. Они не создают прямые runtime- или type-only зависимости с уже переведёнными пакетами.

View File

@@ -11,6 +11,8 @@
- [`SLM-L2-ERROR-R009`](../../rules/level-2.md#slm-l2-error-r009)
- [`SLM-L2-ERROR-R010`](../../rules/level-2.md#slm-l2-error-r010)
- [`SLM-L2-BUSINESS-R018`](../../rules/level-2.md#slm-l2-business-r018)
- [`SLM-L2-BUSINESS-A019`](../../rules/level-2.md#slm-l2-business-a019)
- [`SLM-L2-BUSINESS-A022`](../../rules/level-2.md#slm-l2-business-a022)
## Роль
@@ -28,10 +30,13 @@
## Публичный API модуля
```ts
export { AUTH_ERROR_CODES, isAuthError } from './errors/auth-error'
export { authFactory } from './auth.factory'
Один логический API `business` разделён на три фиксированных фасета.
### Type-only barrel
Корневой `business/index.ts` экспортирует только типы:
```ts
export type {
AuthApi,
AuthDeps,
@@ -42,7 +47,57 @@ export type {
} from './types'
```
Фабрика, error contract и типы экспортируются для presets, adapters, framework-модулей и мест сборки графа. Предметные validators, normalizers, внутренние преобразователи исходных ошибок, constructors, mutable store и технические DTO остаются закрытыми и используются публичными сценариями `DomainApi`.
Потребитель использует этот путь только через `import type`:
```ts
import type {
AuthApi,
AuthError,
AuthErrorCode,
} from '@/domains/auth/business'
```
### Factory entry
`business/factory.ts` экспортирует только runtime-фабрику:
```ts
export { authFactory } from './auth.factory'
```
```ts
import { authFactory } from '@/domains/auth/business/factory'
```
### Error entry
`business/error.ts` экспортирует только runtime-коды и guards:
```ts
export { AUTH_ERROR_CODES, isAuthError } from './errors/auth-error'
```
```ts
import {
AUTH_ERROR_CODES,
isAuthError,
} from '@/domains/auth/business/error'
```
`AuthError` и `AuthErrorCode` не реэкспортируются из `business/error`: все public types имеют один канонический путь через type-only barrel. Предметные validators, normalizers, constructors ошибок, source-error mappers, mutable store и технические DTO остаются закрытыми.
Другие внешние пути внутри `business` являются deep imports. Файлы `factory.ts` и `error.ts` являются фасетами одного SLM-модуля, а не сегментами или вложенными модулями.
## Потребители фасетов
| Потребитель | `business` | `business/factory` | `business/error` |
|---|---|---|---|
| Adapter module своего домена | Type-only | Нет | Нет |
| Preset своего домена | Type-only | Да | Нет |
| Framework binding module своего домена | Type-only | Нет | Да |
| `composition` или `app` | Type-only | Да | Да |
| Модуль другого доменного пакета | Type-only | Нет | Нет |
| Тест | Type-only | По границе тестируемого владельца | По границе тестируемого владельца |
## Один DomainApi
@@ -56,13 +111,13 @@ export type AuthApi = {
export type AuthFactory = (deps: AuthDeps) => AuthApi
```
Все presets вызывают одну `authFactory` и создают `AuthApi` этого контракта. Preset может использовать другую техническую реализацию, но не добавляет метод и не меняет семантику сценария.
Все presets вызывают одну `authFactory` и создают `AuthApi` этого контракта. Preset может использовать другую техническую реализацию, но не добавляет метод и не меняет семантику сценария. Одноразовое место сборки в `composition` также может вызвать `business/factory`, используя публичные adapter-модули пакета, если фабрика имеет технические зависимости.
Точная модель хранения, initial state, подписки и SSR snapshot пока остаётся открытым вопросом. Нормативной уже является публичная граница: приложение наблюдает доменное состояние через `DomainApi`, а не напрямую через adapter или framework store.
## Обязательный контракт ошибок
Каждый `business` объявляет устойчивые коды, безопасную readonly-форму и runtime guard:
Каждый `business` объявляет устойчивые коды, безопасную readonly-форму и runtime guard. Типы публикуются через `business`, а runtime symbols через `business/error`:
```ts
export const AUTH_ERROR_CODES = {

View File

@@ -8,6 +8,10 @@
- [`SLM-L2-DOMAIN-A003`](../../rules/level-2.md#slm-l2-domain-a003)
- [`SLM-L2-GROUP-R004`](../../rules/level-2.md#slm-l2-group-r004)
- [`SLM-L2-BUSINESS-R005`](../../rules/level-2.md#slm-l2-business-r005)
- [`SLM-L2-BUSINESS-A019`](../../rules/level-2.md#slm-l2-business-a019)
- [`SLM-L2-PRESET-A020`](../../rules/level-2.md#slm-l2-preset-a020)
- [`SLM-L2-ADAPTER-R021`](../../rules/level-2.md#slm-l2-adapter-r021)
- [`SLM-L2-BUSINESS-A022`](../../rules/level-2.md#slm-l2-business-a022)
## Предметная граница
@@ -32,7 +36,9 @@ domains/auth/
- ownership metadata;
- декларативный manifest или декларативная конфигурация архитектурной проверки;
- обязательный модуль `business`;
- Groups допустимых ролей.
- обязательная непустая Group `presets`;
- непустая Group `adapters`, если фабрика имеет технические зависимости;
- Framework Groups при наличии соответствующих модулей.
В корне запрещены:
@@ -46,19 +52,24 @@ Metadata содержит только статические данные, не
## Модули и Groups
`business` размещается непосредственно в пакете. Presets и самостоятельные adapters размещаются в Groups `presets` и `adapters`. Framework Group называется по фреймворку: `react`, `vue` и аналогично.
`business` размещается непосредственно в пакете и предоставляет три публичных фасета: type-only barrel, `factory` и `error`. Presets размещаются в обязательной Group `presets`. Все production adapters являются самостоятельными модулями Group `adapters` и не определяются в других частях production-графа. Framework Group называется по фреймворку: `react`, `vue` и аналогично.
```text
auth/
├── business/ # SLM-модуль
├── presets/ # Group
│ ├── index.ts # Только public types
│ ├── factory.ts # Public factory entry
│ └── error.ts # Public error runtime entry
├── adapters/ # Group при наличии technical dependencies
│ └── phone-http/ # SLM-модуль
├── presets/ # Обязательная Group
│ └── browser/ # SLM-модуль
└── react/ # Framework Group
├── session/ # SLM-модуль
└── login-form/ # SLM-модуль
```
Groups не имеют `index.ts`. Поэтому публичными путями являются `auth/business`, `auth/presets/browser`, `auth/react/session`, но не `auth`, `auth/presets` или `auth/react`.
Groups не имеют `index.ts`. Поэтому публичными путями являются `auth/business`, `auth/business/factory`, `auth/business/error`, `auth/adapters/phone-http`, `auth/presets/browser` и `auth/react/session`, но не `auth`, `auth/adapters`, `auth/presets` или `auth/react`.
## Навигационные Groups

View File

@@ -8,6 +8,9 @@
- [`SLM-L2-FACTORY-R008`](../../rules/level-2.md#slm-l2-factory-r008)
- [`SLM-L2-ERROR-R010`](../../rules/level-2.md#slm-l2-error-r010)
- [`SLM-L2-DEPENDENCY-A012`](../../rules/level-2.md#slm-l2-dependency-a012)
- [`SLM-L2-BUSINESS-A019`](../../rules/level-2.md#slm-l2-business-a019)
- [`SLM-L2-ADAPTER-R021`](../../rules/level-2.md#slm-l2-adapter-r021)
- [`SLM-L2-BUSINESS-A022`](../../rules/level-2.md#slm-l2-business-a022)
## Одна фабрика
@@ -18,9 +21,17 @@
Модуль `business` предоставляет одну публичную фабрику. Она получает все runtime-возможности явными аргументами и создаёт API одного контракта независимо от выбранного preset.
```ts
import type { AuthApi, AuthDeps } from '@/domains/auth/business'
export type AuthFactory = (deps: AuthDeps) => AuthApi
```
Runtime-фабрика импортируется только через отдельный entry point:
```ts
import { authFactory } from '@/domains/auth/business/factory'
```
Рекомендуется сохранять сам вызов фабрики чистым: он создаёт объекты и closures, но не читает скрытое окружение и не выбирает конкретную техническую реализацию. Детальный lifecycle ресурсов будет нормирован позже.
## Технические зависимости
@@ -50,11 +61,11 @@ export type UserDeps = {
}
```
Runtime-значение передаёт место сборки графа через preset. `user/business` не импортирует executable API, factory или preset Auth.
Runtime-значение место сборки графа передаёт через preset либо напрямую зависимой business-фабрике. `user/business` не импортирует executable API, factory или preset Auth.
## Adapter
## Adapter module
Adapter соединяет явную зависимость фабрики с технической системой:
Adapter module соединяет явную техническую зависимость фабрики с конкретной системой:
```text
business dependency ← adapter → SDK / storage / platform / request data
@@ -64,23 +75,40 @@ Adapter преобразует аргументы и технический ре
Ожидаемый исходный сбой возвращается `business`, который выбирает собственный error code. Поэтому приложение никогда не строит поведение по HTTP status, SDK error class или storage exception.
## Размещение adapter
## Размещение adapters
Одноразовый adapter остаётся закрытым сегментом preset-модуля:
```text
auth/presets/browser/
├── adapters/
│ └── phone.adapter.ts
└── index.ts
```
Adapter становится самостоятельным SLM-модулем, когда нужен нескольким presets или имеет отдельную integration responsibility:
Каждая production-реализация является отдельным SLM-модулем в Group `adapters`, даже если пока используется одним preset:
```text
auth/adapters/
── identity-provider/
── phone-http/
│ └── index.ts
└── browser-session/
└── index.ts
```
Самостоятельный adapter сохраняет минимальный публичный API и не становится альтернативным источником доменных данных для приложения.
Adapter module имеет собственные ответственность, публичный API, environment label и тестовую границу. Group `adapters` не имеет `index.ts` и не реэкспортирует дочерние модули.
Production adapter запрещено определять:
- закрытым сегментом preset;
- inline-функцией в `composition` или `app`;
- частью framework binding module;
- скрытой реализацией внутри `business`.
Preset и одноразовое место сборки импортируют конкретные adapter-модули через их публичные API:
```ts
import { authFactory } from '@/domains/auth/business/factory'
import { createPhoneHttpAdapter } from '@/domains/auth/adapters/phone-http'
import { createBrowserSessionAdapter } from '@/domains/auth/adapters/browser-session'
const authApi = authFactory({
phone: createPhoneHttpAdapter(),
session: createBrowserSessionAdapter(),
})
```
Если фабрика не имеет технических зависимостей, Group `adapters` не обязательна. Cross-domain API dependency не считается adapter и передаётся отдельно.
Локальные fake implementations в business-тестах не являются production adapters и не требуют SLM-модулей. Они существуют только внутри тестовой границы и не экспортируются в рабочий код.

View File

@@ -7,6 +7,8 @@
- [`SLM-L2-DEPENDENCY-A012`](../../rules/level-2.md#slm-l2-dependency-a012)
- [`SLM-L2-FRAMEWORK-R014`](../../rules/level-2.md#slm-l2-framework-r014)
- [`SLM-L2-FRAMEWORK-R015`](../../rules/level-2.md#slm-l2-framework-r015)
- [`SLM-L2-BUSINESS-A019`](../../rules/level-2.md#slm-l2-business-a019)
- [`SLM-L2-BUSINESS-A022`](../../rules/level-2.md#slm-l2-business-a022)
## Framework Group
@@ -41,6 +43,22 @@ Framework binding module может:
Он не вызывает business-фабрику или preset, не выбирает adapters и не создаёт новые предметные сценарии.
Framework binding module импортирует типы и runtime error contract через разные фасеты:
```ts
import type {
AuthApi,
AuthError,
} from '@/domains/auth/business'
import {
AUTH_ERROR_CODES,
isAuthError,
} from '@/domains/auth/business/error'
```
Импорт `business/factory` из Framework Group запрещён: готовый `DomainApi` передаётся модулю извне.
## Модуль session
`auth/react/session` может владеть Provider и hooks доступа к уже созданному `AuthApi`:

View File

@@ -8,10 +8,14 @@
- Level 2 заменяет доменный модуль доменным пакетом.
- Корень пакета содержит только metadata, модули и Groups и не имеет executable API.
- `business` предоставляет одну фабрику и один `DomainApi`.
- Публичный API `business` разделён на type-only barrel, `business/factory` и `business/error`; другие пути запрещены.
- Приложение получает доменные данные, состояние и результаты только через `DomainApi`.
- Каждый `business` экспортирует коды, тип и runtime guard доменных ошибок.
- Ожидаемые ошибки adapters и других доменов не пересекают API текущего домена.
- Количество presets определяется реальными окружениями; универсальный preset не обязателен.
- Каждый доменный пакет содержит минимум один preset; универсальный изоморфный preset не обязателен.
- При наличии технических зависимостей Group `adapters` обязательна, а каждая production implementation является отдельным SLM-модулем.
- Все production consumers используют публичные adapter-модули; inline adapter implementations вне Group `adapters` запрещены.
- Одноразовая composition может вызвать `business/factory` напрямую; это не отменяет обязательный preset пакета.
- Runtime cross-domain imports запрещены; type-only business contracts разрешены и входят в DAG.
- Framework Group называется по фреймворку и содержит самостоятельные SLM-модули.
- Cross-domain framework state, hooks, contexts и components не импортируются.

View File

@@ -8,10 +8,14 @@
- [`SLM-L2-PRESET-R011`](../../rules/level-2.md#slm-l2-preset-r011)
- [`SLM-L2-DEPENDENCY-A012`](../../rules/level-2.md#slm-l2-dependency-a012)
- [`SLM-L2-ENVIRONMENT-A013`](../../rules/level-2.md#slm-l2-environment-a013)
- [`SLM-L2-BUSINESS-A019`](../../rules/level-2.md#slm-l2-business-a019)
- [`SLM-L2-PRESET-A020`](../../rules/level-2.md#slm-l2-preset-a020)
- [`SLM-L2-ADAPTER-R021`](../../rules/level-2.md#slm-l2-adapter-r021)
- [`SLM-L2-BUSINESS-A022`](../../rules/level-2.md#slm-l2-business-a022)
## Назначение
Preset является SLM-модулем в Group `presets`. Он создаёт API одной business-фабрики для конкретного повторяемого контекста выполнения.
Preset является SLM-модулем в Group `presets`. Он создаёт API одной business-фабрики для конкретного повторяемого контекста выполнения. Каждый доменный пакет содержит минимум один preset-модуль.
```text
authFactory
@@ -20,29 +24,39 @@ authFactory
└── presets/server-action → AuthApi server action
```
Архитектура не требует обязательный `base` или изоморфный preset и не ограничивает количество presets. Проект создаёт только те сборки, которые нужны его реальным средам и областям использования.
Архитектура не требует `base` или изоморфный preset и не ограничивает максимальное количество presets. Обязательный preset должен соответствовать реальному поддерживаемому контексту, а не существовать только для заполнения структуры.
Если фабрика используется в одном месте и отдельная повторяемая конфигурация не возникает, место сборки графа может вызвать её напрямую.
Место сборки графа в `composition` может вызвать фабрику напрямую для одноразовой конфигурации. Такая сборка не отменяет обязательный preset пакета и при наличии технических зависимостей использует публичные adapter-модули, а не inline implementations.
## Один контракт API
Каждый preset выбирает технические реализации, но вызывает одну и ту же фабрику и возвращает один контракт `DomainApi`:
Каждый preset вызывает одну и ту же фабрику и возвращает один контракт `DomainApi`. При наличии технических зависимостей preset выбирает их публичные adapter-модули:
```ts
import type { AuthApi } from '@/domains/auth/business'
import { authFactory } from '@/domains/auth/business/factory'
import { createPhoneHttpAdapter } from '@/domains/auth/adapters/phone-http'
import { createBrowserSessionAdapter } from '@/domains/auth/adapters/browser-session'
export const createBrowserAuth = (): AuthApi => {
return authFactory({
phone: createHttpPhoneAdapter(),
phone: createPhoneHttpAdapter(),
session: createBrowserSessionAdapter(),
})
}
```
```ts
import type { AuthApi } from '@/domains/auth/business'
import { authFactory } from '@/domains/auth/business/factory'
import { createRequestPhoneAdapter } from '@/domains/auth/adapters/request-phone'
import { createRequestSessionAdapter } from '@/domains/auth/adapters/request-session'
export const createAuthForRequest = (
input: AuthRequestInput,
): AuthApi => {
return authFactory({
phone: createServerPhoneAdapter(input),
phone: createRequestPhoneAdapter(input),
session: createRequestSessionAdapter(input),
})
}
@@ -54,10 +68,13 @@ Server preset может обращаться к database напрямую че
## Cross-domain input
Preset зависимого домена принимает готовый API аргументом:
Preset зависимого домена принимает готовый API аргументом. Cross-domain API не является adapter и не размещается в Group `adapters`:
```ts
import type { AuthApi } from '@/domains/auth/business'
import type { UserApi } from '@/domains/user/business'
import { userFactory } from '@/domains/user/business/factory'
import { createUserProfileAdapter } from '@/domains/user/adapters/profile'
export type CreateUserForRequestInput = {
authApi: Pick<AuthApi, 'getSession'>

View File

@@ -2,9 +2,13 @@
> Проверка владельцев и публичных границ Level 2.
## Связанное правило
## Связанные правила
- [`SLM-L2-TEST-R016`](../../rules/level-2.md#slm-l2-test-r016)
- [`SLM-L2-BUSINESS-A019`](../../rules/level-2.md#slm-l2-business-a019)
- [`SLM-L2-PRESET-A020`](../../rules/level-2.md#slm-l2-preset-a020)
- [`SLM-L2-ADAPTER-R021`](../../rules/level-2.md#slm-l2-adapter-r021)
- [`SLM-L2-BUSINESS-A022`](../../rules/level-2.md#slm-l2-business-a022)
## Размещение
@@ -23,7 +27,14 @@
Каждый публичный предметный сценарий проверяется через единственную фабрику с управляемыми зависимостями:
```ts
const api = authFactory(createAuthTestDeps({
import type { AuthApi } from '@/domains/auth/business'
import { authFactory } from '@/domains/auth/business/factory'
import {
AUTH_ERROR_CODES,
isAuthError,
} from '@/domains/auth/business/error'
const api: AuthApi = authFactory(createAuthTestDeps({
requestCode: async () => ({ ok: true }),
}))
@@ -32,25 +43,32 @@ await api.requestPhoneOtp('+79991112233')
Набор проверяет успешные и ожидаемые ошибочные результаты, validation, преобразование внешних данных и публичные изменения состояния. Способ assertion для ошибки зависит от будущего решения `throw` или `Result`, но наружу всегда проверяется только AuthErrorCode.
Business-тест не использует React, реальный SDK, database или production preset.
Business-тест не использует React, реальный SDK, database или production preset. Локальная fake implementation допустима в тесте и не становится adapter-модулем, потому что не входит в production graph.
## Остальные модули
Adapter-тест проверяет технический вызов, аргументы, преобразование результата и передачу исходного сбоя business-слою. Он не повторяет mapping в доменные ошибки.
Тест каждого adapter-модуля проверяет технический вызов, аргументы, преобразование результата и передачу исходного сбоя business-слою. Он не повторяет mapping в доменные ошибки.
Preset-тест проверяет выбранные реализации, вызов одной фабрики, контракт возвращённого `DomainApi` и отсутствие несовместимого environment-кода.
Тест обязательного preset проверяет вызов `business/factory`, контракт возвращённого `DomainApi` и отсутствие несовместимого environment-кода. Если фабрика имеет технические зависимости, тест также проверяет выбранные публичные adapter-модули; adapterless preset проверяет корректную сборку без Group `adapters`.
Framework-тест импортирует только конкретный модуль, например `auth/react/session`, передаёт тестовый `AuthApi` и проверяет Provider, hook или component. `login-form` не повторяет полный набор business-сценариев.
## Архитектурные проверки
## Автоматические структурные проверки
Отдельная import-graph проверка подтверждает:
Проверка файлов, exports и import-графа подтверждает:
- отсутствие root API доменного пакета и Framework Groups;
- наличие ровно трёх фасетов `business`, type-only exports в корневом barrel и отсутствие type exports в runtime-фасетах;
- соблюдение матрицы потребителей `business`, `business/factory` и `business/error`;
- наличие непосредственно в корне пакета непустой Group `presets` с объявленными модульными границами;
- отсутствие runtime cross-domain imports;
- отсутствие type-only импортов из чужих presets, adapters и framework-модулей;
- отсутствие cross-domain framework hooks, contexts и components;
- отсутствие server-only достижимости из client modules;
- отсутствие runtime- и type-only циклов.
Runtime-тест не заменяет эти проверки.
## Архитектурное ревью
На ревью проверяется, что `business/factory` экспортирует только фабрику, а `business/error` только error codes и guards. Для каждой технической зависимости рассматриваются все production implementations: каждая должна принадлежать отдельному модулю Group `adapters`, даже если используется один раз. Inline implementations во всём production-графе запрещены, а test-only fakes из этой проверки исключены.
Runtime-тест не заменяет автоматическую проверку или архитектурное ревью.