feat: Полное переосмысление документации, v2 DRAFT

This commit is contained in:
2026-07-24 22:34:49 +03:00
parent 192a8a185b
commit 4fad7e712a
25 changed files with 2363 additions and 0 deletions

View File

@@ -0,0 +1,543 @@
# Реорганизация Business-модуля
> Статус: proposal для обсуждения. Это не принятый канон и не инструкция для механической миграции.
## Решение, которое нужно принять
`business/{domain}` должен быть вертикальным продуктовым модулем, а не только чистым источником доменных данных.
Один business-модуль владеет:
- доменной моделью, правилами, сценариями и ошибками;
- доменным состоянием и его переходами;
- адаптацией внешних возможностей к потребностям домена;
- React hooks, Provider и UI, выражающими один домен;
- отдельными server/client entrypoints, когда этого требует runtime Next.js.
React не является причиной вынести domain hooks, store или domain UI в `compositions`. Он является входным runtime-адаптером того же домена.
`compositions` остаётся местом, где связываются несколько доменов, выбирается scope graph и собирается page/route UI. Но он не становится владельцем `useAuth`, `AuthProvider`, `LoginForm` или domain store.
## Почему текущая модель не подходит
Logic-only business делит один домен по технической природе файлов:
```text
business/auth # types, factory, scenarios
compositions/business/auth # concrete runtime adapters
compositions/pages/login # provider, hooks, domain UI
```
В результате у `auth` нет одной физической и понятной границы. Доменное поведение, его runtime, доступ из React и UI существуют отдельно, хотя меняются совместно.
Особенно проблемны следующие свойства.
1. Hooks остаются частью public API domain, но их React execution model прячется за `deps`. Business формально не импортирует React, но API всё равно нельзя вызвать из Server Component, обычной функции или теста без React render context.
2. Domain state имеет трёх владельцев: модель и transitions в `business`, concrete store в integration composition, instance и lifecycle в page Provider. Невозможно коротко ответить, где находится `auth`.
3. Правило «единственный runtime export - фабрика» защищает от обхода DI, но одновременно запрещает безопасные domain hooks, Provider и компоненты. Оно ограничивает форму public API вместо утечки реализации.
4. Разделение server/client - это граница module graph и runtime, а не две разные продуктовые ответственности. Перенос client-кода в `compositions` не решает границу, а скрывает её.
5. Consumer composition вынужден знать, как React подключается к домену, хотя это часть внутренней реализации domain runtime.
Цель реорганизации - вернуть business-модулю вертикальное владение, не потеряв полезные инварианты текущей модели: ports, domain errors, DI, минимальные API, lifecycle и отсутствие runtime-циклов.
## Новое определение Business-модуля
> Business-модуль - минимальная вертикальная граница продуктового домена. Он создаёт domain runtime через фабрику и предоставляет этому runtime несколько явных интерфейсов: обычный TypeScript API, React API и при необходимости server API.
Это один модуль и один owner. Внутри него допустимы несколько зон с разными правилами зависимостей.
```text
business/auth/
├── kernel/ # доменная логика без React и concrete runtime
├── adapters/ # реализации исходящих ports
├── react/ # client-only React API домена
├── server/ # server-only сборка, только при необходимости
├── auth.factory.ts # создание domain runtime
├── index.ts # universal public API
├── react.ts # client-only public entrypoint
└── server.ts # server-only public entrypoint, optional
```
`kernel`, `adapters`, `react` и `server` - зоны ответственности внутри одного business-модуля. Они не являются новыми SLM-слоями и не должны автоматически появляться во всех доменах.
## Зоны и зависимости
### Kernel
`kernel/` содержит то, что определяет домен независимо от платформы:
- domain types и value objects;
- domain errors со стабильными `code`;
- правила, validators, normalizers и mappers;
- use cases, commands, queries и selectors;
- модель domain state, transitions и initial state;
- контракты исходящих ports.
`kernel` не импортирует:
- React, Next.js, browser API;
- SDK, HTTP client, storage implementation;
- Zustand, Redux, SWR, TanStack Query и другие concrete runtimes;
- `compositions`, `app` или UI.
Именно kernel делает business runtime переносимым между server, browser и test environment.
### Factory
Фабрика остаётся единственным способом создать instance domain runtime. Она принимает минимальный набор ports и возвращает поведение домена.
```ts
export type AuthPorts = {
session: {
getCurrent: () => Promise<unknown>
signIn: (input: SignInInput) => Promise<unknown>
signOut: () => Promise<void>
}
tokenStorage: {
read: () => string | null
write: (token: string | null) => void
}
}
export type AuthRuntime = {
getCurrentUser: () => Promise<User | null>
signIn: (input: SignInInput) => Promise<Session>
signOut: () => Promise<void>
getSnapshot: () => AuthState
subscribe: (listener: () => void) => () => void
}
export const authFactory = (ports: AuthPorts): AuthRuntime => {
// Создаёт domain state, commands, queries и selectors.
}
```
Фабрика:
- не создаёт HTTP client, browser storage или query client;
- не запускает I/O, subscriptions или timers при создании instance;
- не импортирует React;
- не возвращает raw store, raw context, SDK client или adapter;
- может возвращать methods, selectors, subscriptions и другие framework-neutral runtime operations.
Фабрика не обязана быть единственным runtime export модуля. Она является единственным местом создания business runtime.
### Adapters
`adapters/` реализует исходящие ports kernel поверх конкретной технологии:
```text
business/auth/adapters/
├── auth-api.adapter.ts
├── browser-token-storage.adapter.ts
└── server-token-storage.adapter.ts
```
Адаптер нужен, когда concrete dependency не соответствует domain port или должен остаться внутренней деталью домена.
```ts
export const createAuthSessionAdapter = (
apiClient: AuthApiClient,
): AuthPorts['session'] => ({
getCurrent: () => apiClient.auth.getCurrentSession(),
signIn: (input) => apiClient.auth.signIn({ body: input }),
signOut: () => apiClient.auth.signOut(),
})
```
Назначение адаптера не в том, чтобы «упростить вызов фабрики». Он сохраняет границу:
- kernel знает потребность домена, а не форму SDK;
- transport payload и вызов конкретного API не становятся business contract;
- browser и server могут реализовать один port по-разному;
- замена SDK, storage или event runtime не требует менять kernel.
Адаптер:
- импортирует port type из kernel и concrete `infra` dependency;
- преобразует domain input в transport input;
- возвращает raw result или source error в kernel для нормализации и error mapping;
- не формирует domain model, domain error или fallback;
- не экспортируется из public entrypoint.
Адаптер не является обязательным ритуалом. Если конкретная dependency уже точно реализует domain port и не протекает через public API, её можно передать фабрике напрямую. Не нужно создавать файл-обёртку из одной строки без адаптации или runtime-границы.
Domain-specific adapter живёт рядом с доменом, потому что только он знает, какой endpoint или storage key реализует именно `AuthPorts`. Общий технический client, transport, logger или storage primitive остаётся в `infra`.
### React
`react/` - client-only входной адаптер domain runtime. Он содержит:
- закрытый Context;
- Provider, принимающий или создающий текущий `AuthRuntime`;
- access hooks, например `useAuthRuntime`;
- domain hooks, например `useAuth`, `useCurrentUser`, `usePermissions`;
- client state/query integration, если она выражает доменное состояние;
- domain-specific interactive UI, например `LoginForm` и `LogoutButton`.
```text
business/auth/react/
├── auth.provider.tsx
├── hooks/
│ ├── use-auth-runtime.ts
│ ├── use-auth.ts
│ └── use-current-user.ts
├── ui/
│ ├── login-form.tsx
│ └── logout-button.tsx
└── create-auth-browser-runtime.ts
```
React-код может импортировать kernel, factory и client-specific adapters. Обратный импорт запрещён:
```text
react → factory → kernel
react → browser adapter → infra
kernel -/→ react
factory -/→ react
```
Provider связывает статически экспортируемый React API с конкретным instance, созданным фабрикой:
```tsx
'use client'
const AuthRuntimeContext = createContext<AuthRuntime | null>(null)
export function AuthProvider({
runtime,
children,
}: {
runtime: AuthRuntime
children: ReactNode
}) {
return (
<AuthRuntimeContext.Provider value={runtime}>
{children}
</AuthRuntimeContext.Provider>
)
}
```
```ts
export function useAuthRuntime(): AuthRuntime {
const runtime = useContext(AuthRuntimeContext)
if (!runtime) {
throw new Error('AuthProvider is missing')
}
return runtime
}
```
```ts
export function useCurrentUser() {
const auth = useAuthRuntime()
return useQuery({
queryKey: ['auth', 'current-user'],
queryFn: auth.getCurrentUser,
})
}
```
`useCurrentUser` принадлежит `auth`, хотя использует Query runtime: он выражает доменное понятие и работает только с `AuthRuntime`. Он не вызывает SDK и не выполняет domain normalization самостоятельно.
Компоненты из `react/ui/` могут вызывать hooks своего домена и использовать универсальные компоненты из `ui`. Они не должны:
- напрямую обращаться к SDK, storage или browser API;
- реализовывать business rule, error mapping или нормализацию;
- импортировать runtime другого домена;
- собирать UI из нескольких доменов.
Например, `LoginForm` принадлежит `auth`; `CheckoutHeader`, объединяющий `auth`, `cart` и `orders`, принадлежит composition module.
### Server
`server/` - optional зона для server-only assembly домена. Она нужна только если у домена действительно есть server-specific ports или запросный scope.
```text
business/auth/server/
└── create-auth-server-runtime.ts
```
Server builder создаёт новый runtime на request scope и передаёт в фабрику server adapters:
```ts
import 'server-only'
export function createAuthServerRuntime(
input: AuthServerScopeInput,
): AuthRuntime {
return authFactory({
session: createServerSessionAdapter(input),
tokenStorage: createRequestTokenStorageAdapter(input),
})
}
```
`server/` не импортирует `react/`, а `react/` не импортирует `server/`. Два runtime создаются над одной factory/kernel, но имеют разные lifecycle и concrete ports.
Создавать `server/` симметрично в каждом домене не нужно. Если server использует только universal factory с явно переданными testable ports, отдельный entrypoint не добавляется.
## Public entrypoints
Ограничивать public API только фабрикой не нужно. Следует разделять public entrypoints по runtime, чтобы Next.js мог построить корректные module graphs.
```ts
// business/auth/index.ts
export { authFactory } from './auth.factory'
export type {
AuthPorts,
AuthRuntime,
AuthState,
SignInInput,
User,
} from './kernel'
```
```ts
// business/auth/react.ts
'use client'
export { AuthProvider } from './react/auth.provider'
export { useAuth, useAuthRuntime, useCurrentUser } from './react/hooks'
export { LoginForm, LogoutButton } from './react/ui'
export { createAuthBrowserRuntime } from './react/create-auth-browser-runtime'
```
```ts
// business/auth/server.ts
import 'server-only'
export { createAuthServerRuntime } from './server/create-auth-server-runtime'
export type { AuthServerScopeInput } from './server/auth-server-scope-input.type'
```
Правила:
- `index.ts` не импортирует и не реэкспортирует `react` или `server`;
- `react.ts` является client entrypoint и не импортирует `server`;
- `server.ts` является server entrypoint и не импортирует `react`;
- public API может содержать стабильные pure functions, factory, hooks, Provider и domain UI в соответствующем entrypoint;
- public API не раскрывает raw context, raw store, mutable singleton, persistence key, SDK client или private adapter;
- каждый runtime export имеет реального внешнего consumer.
Это не ослабление encapsulation. Оно заменяет запрет «runtime export вообще» на полезный запрет «не раскрывай concrete mutable implementation и не смешивай runtime graphs».
## SSR и RSC
Разделение entrypoints существует для Next.js module graph, а не для разделения business ownership.
```text
Server Component
→ @/business/auth/server или @/business/auth
→ authFactory
→ server ports/adapters
Client Component
→ @/business/auth/react
→ AuthProvider + hooks + UI
→ browser runtime
→ authFactory
→ browser ports/adapters
```
Один `AuthRuntime` нельзя передавать из Server Component в Client Component: он содержит functions и не сериализуется через RSC. Через границу передаются только serializable domain data, например `AuthSnapshot`, `User` или initial form state.
Server runtime:
- создаётся на request scope;
- получает headers, cookies, request ID и abort signal только через server input/adapters;
- не становится module singleton;
- не использует browser storage или React state.
Browser runtime:
- создаётся на Provider или client graph scope;
- не использует request credentials другого пользователя;
- не запускает I/O, subscriptions или timers во время module import;
- получает server bootstrap data только как serializable input.
Провайдер может принимать готовый browser runtime. Convenience API, который создаёт runtime внутри Provider, допустим только если constructor side-effect free и lifecycle явно определён. Этот выбор нужно проверить на полном примере до фиксации канона.
## Роль Compositions после реорганизации
`compositions` не владеет domain hooks, domain Provider, domain UI или domain adapters. Он отвечает за связи между модулями:
- собирает граф из нескольких готовых domain runtimes;
- выбирает application, route, page или request scope;
- передаёт суженные cross-domain dependencies при создании runtime;
- монтирует domain Providers в нужной точке React tree;
- реализует UI и orchestration, объединяющие несколько доменов;
- владеет page-local presentation state.
```tsx
const authRuntime = createAuthBrowserRuntime()
const profileRuntime = createProfileBrowserRuntime({
auth: pickAuthForProfile(authRuntime),
})
return (
<AuthProvider runtime={authRuntime}>
<ProfileProvider runtime={profileRuntime}>
<ProfilePage />
</ProfileProvider>
</AuthProvider>
)
```
Это не означает, что composition реализует `AuthProvider` или `useAuth`: она только использует public React API домена и создаёт graph в scope, который реально им владеет.
## State и lifecycle
Нужно различать три разные ответственности.
| Ответственность | Владелец |
|---|---|
| Модель state, transitions, selectors, commands | kernel домена |
| Concrete implementation, например Zustand или Query | `react/` или runtime-specific adapter домена |
| Количество instances и время жизни | graph owner: Provider, route, page, application или request scope |
Concrete store не должен экспортироваться. React API отдаёт selectors, commands и hooks:
```ts
export function useAuthState(): AuthState {
const auth = useAuthRuntime()
return useSyncExternalStore(
auth.subscribe,
auth.getSnapshot,
auth.getSnapshot,
)
}
```
Если Zustand, Redux или другой state runtime полезен только как техническая реализация, он должен быть закрыт в `react/` или adapter. Если его модель становится частью domain API, сначала нужно описать стабильный domain contract, а не экспортировать `StoreApi`.
Любые subscription, timer, socket и event listener:
- запускаются после mount/commit или в явно названной `start` operation;
- возвращают cleanup;
- не запускаются при import или создании factory instance;
- не делают browser runtime частью server graph.
## Domain UI и граница с Compositions
Возврат UI в business не означает, что любой UI становится domain UI.
| Сущность | Владелец |
|---|---|
| `Button`, `Modal`, `Tabs`, input primitive | `ui` |
| `LoginForm`, `LogoutButton`, `AuthRequired` | `business/auth/react/ui` |
| `CartSummary`, `AddToCartButton` | соответствующий business-домен |
| Header, объединяющий Auth, Cart и Navigation | `compositions` |
| Page, route, layout и screen | `compositions` |
| Sidebar open state, active tab и page-only flow | соответствующая composition |
Критерий для domain UI:
> Если сущность нельзя назвать и использовать без терминов конкретного домена, она вероятно принадлежит business-модулю.
Критерий для composition UI:
> Если сущность связывает несколько доменов, route/page scope или формирует конкретный экран, она принадлежит composition module.
## Что сохранить из текущей модели
Реорганизация не должна вернуть проблемы старого permissive подхода. Сохраняются следующие инварианты.
1. Concrete SDK, DTO и raw external errors не становятся public contract домена.
2. Домен нормализует внешние данные и наружу выдаёт только domain model и domain errors.
3. Cross-domain runtime dependencies передаются при сборке graph, сужаются до необходимого API и не образуют циклы.
4. Factory constructors и adapter constructors не делают I/O.
5. Lifecycle subscription/resource явно запускается и очищается владельцем scope.
6. Mutable internals, raw contexts, stores, adapters и clients закрыты от consumers.
7. Common UI остаётся в `ui`; domain UI не получает право быть бесконтрольным feature layer.
8. Product data не извлекается напрямую из SDK в page/screen/component, если она уже принадлежит domain scenario.
## Что меняется относительно текущей модели
| Текущая идея | Предложение |
|---|---|
| Business содержит только logic API | Business владеет vertical domain: kernel, runtime adapters и React API |
| React hooks - dependency wrapper business | Hooks - client input adapter домена |
| Concrete adapters в `compositions/business/{domain}` | Domain-specific adapters рядом с доменом; `infra` хранит только общие техсервисы |
| Factory - единственный runtime export | Factory - единственный creator domain runtime; public entrypoints могут экспортировать hooks, Provider и UI |
| Business не содержит UI | Domain-specific UI находится в `business/{domain}/react/ui` |
| Provider реализуется в composition | Domain Provider реализуется в business; composition выбирает место mount и graph scope |
| Каждый домен обязан иметь одинаковую server/client форму | `server/` добавляется только при реальной server-only необходимости |
## Не цели
- Не нужно создавать `kernel`, `adapters`, `react` и `server` в каждом модуле заранее.
- Не нужно переносить в business page/layout/route UI.
- Не нужно возвращать прямые SDK calls из hooks или компонентов.
- Не нужно создавать generic global `BusinessProvider` или service locator.
- Не нужно вводить module singleton как замену явному graph owner.
- Не нужно считать любой `useX` domain hook: многие hooks остаются page-local или universal UI hooks.
- Не нужно механически переносить все существующие `compositions/business/*` без подтверждения ownership на реальном домене.
## Проверочный пример перед изменением канонов
Новая модель должна быть проверена на одном полном домене, предпочтительно `auth`.
Минимальная цепочка:
```text
business/auth/kernel
→ AuthPorts, AuthRuntime, domain errors, session transitions
business/auth/adapters
→ HTTP session adapter, browser storage adapter, request storage adapter
business/auth/react
→ AuthProvider, useAuth, useCurrentUser, LoginForm, LogoutButton
business/auth/server (если нужен)
→ request-scoped AuthRuntime
composition
→ создаёт auth runtime в нужном scope и монтирует AuthProvider
```
Пример должен доказать:
1. Server Component получает current user через server или universal API без client imports.
2. Client Component получает то же доменное состояние через `useAuth` без SDK import.
3. `LoginForm` использует domain commands и domain errors без page-specific wiring.
4. Browser и server используют одну factory/kernel, но разные adapters и instances.
5. Нельзя передать runtime instance через RSC; передаётся только serializable snapshot.
6. Cross-domain dependency можно передать без runtime cycle.
7. Provider/store lifecycle не создаёт I/O при import и не оставляет subscription после unmount.
8. Public entrypoints не тянут server код в client bundle и client code в Server Component.
## Открытые вопросы для следующего шага
Эти вопросы нужно решить на проверочном примере, а не декларацией.
1. Где именно создаётся browser runtime по умолчанию: в graph composition или в convenience Provider домена? Вероятно, нужны обе формы: явный низкоуровневый Provider с `runtime` и ограниченный convenience root для независимого домена.
2. Какие зависимости вправе собирать domain-specific browser builder: только собственные adapters или также API других доменов? Рекомендуемое ограничение: другие domain APIs передаются снаружи как явный input.
3. Должен ли Query cache быть частью domain runtime или только React adapter? Базовая гипотеза: cache - React implementation detail, а domain runtime предоставляет commands/queries и invalidation intent в domain language.
4. В каких случаях domain adapter остаётся в приложении, а не рядом с business? Предлагаемый критерий: если адаптер реализует app-specific integration, недоступную другим приложениям монорепозитория, его можно держать в app assembly, сохраняя тот же port.
5. Нужно ли предоставлять Server Actions в `business/{domain}/server`? Базовая гипотеза: server action - framework entry и остаётся в app/composition; domain server facade предоставляет только business operation.
6. Какой минимальный contract нужен для server hydration domain state, чтобы не дублировать запрос на первом client render?
7. Какие типы domain UI допустимо экспортировать напрямую, а какие должны оставаться private implementation Provider/flow?
## Критерий принятия
Модель стоит переносить в каноны, только если проверочный домен позволяет одновременно ответить «да» на все вопросы:
- У домена один понятный owner, несмотря на server/client runtime surfaces?
- Фабрика по-прежнему является единственным creator business runtime?
- Hooks, Provider и domain UI colocated с доменом, а не вынесены ради технической чистоты?
- Server Component не тянет React/client module graph?
- Client Component не тянет server-only graph?
- Concrete implementation не протекает через public API?
- Сценарии, нормализация и errors не дублируются между server и client?
- Graph ownership и lifecycle instances по-прежнему явны?
- Новый подход проще объяснить и применить, чем текущие `business` + `compositions/business` + consumer composition?
Если хотя бы один ответ отрицательный, сначала нужно скорректировать proposal и проверить его на реальном коде, а не добавлять новые жёсткие правила.

13
docs2/README.md Normal file
View File

@@ -0,0 +1,13 @@
# SLM Design 2.0 Draft
`docs2/` содержит черновик новой спецификации SLM Design.
Текущая документация в `docs/` остаётся действующим источником истины до отдельного решения о принятии новой спецификации. Skill и его generated reference пока не используют `docs2/`.
## Точка входа
[SLM Design Specification](./specification/index.md)
## Границы текущего этапа
На этом этапе в `docs2/` размещается только нормативная спецификация. Учебные материалы, руководства, примеры, справочники и agent skill будут проектироваться после стабилизации правил.

View File

@@ -0,0 +1,94 @@
---
title: Архитектурная модель
status: draft
normative: true
---
# Архитектурная модель
## Структура приложения
**SLM-ARCH-001 - ОБЯЗАН.** SLM-приложение должно разделять код по ответственности между следующими слоями:
```text
src/
├── app/
├── compositions/
├── domains/
├── infra/
├── ui/
└── shared/
```
Не каждый слой обязан содержать код в минимальном приложении, но роль каждого существующего модуля должна соответствовать одному владельцу.
## Группы ответственности
| Группа | Слои | Ответственность |
|---|---|---|
| Framework composition | `app`, `compositions` | Подключение к framework и сборка application flows |
| Product | `domains` | Продуктовые модели, сценарии и runtime surfaces |
| Technical | `infra`, `ui` | Технические capabilities и универсальный UI |
| Foundation | `shared` | Детерминированный общий фундамент |
## Верхнеуровневое направление
```text
app → compositions | shared
compositions → compositions | domains | infra | ui | shared
domains → infra | ui | shared согласно правилам внутренних зон
infra → infra | shared
ui → ui | shared
shared -/→ остальные SLM-слои
```
Схема описывает imports между SLM-слоями проекта. Framework APIs и external packages регулируются ответственностью импортирующего слоя и не показаны как SLM-слои.
**SLM-ARCH-002 - ОБЯЗАН.** Верхнеуровневое направление зависимостей между SLM-слоями должно соблюдаться для runtime imports и type imports, кроме явно описанных исключений.
**SLM-ARCH-003 - ЗАПРЕЩЕНО.** Нижний слой не может импортировать `app` или `compositions`.
**SLM-ARCH-004 - ЗАПРЕЩЕНО.** `infra`, `ui` и `shared` не могут владеть product graph или выступать service locator для domain runtimes.
## Путь данных
```text
app
→ composition
→ domain runtime surface
→ domain business scenario
→ business-owned port
→ domain adapter
→ infra / SDK / storage / external source
```
**SLM-ARCH-005 - ОБЯЗАН.** Каждый переход в цепочке продуктовых данных должен сохранять ownership: framework связывает, domain определяет semantics, adapter интегрирует, infra предоставляет technical capability.
## Путь UI
```text
app route
→ page/layout composition
→ domain UI и composition UI
→ universal UI
→ shared styles/resources
```
Владение multi-domain и domain UI определяется правилами [SLM-CMP-006 - SLM-CMP-007](./layers/compositions.md#product-ui) и [SLM-FRM-013](./layers/domains/framework.md#domain-ui).
## Внутренняя модель domain
```text
domain client/server assembly
→ own business factory
→ own adapters
domain framework surface
→ own DomainRuntime через runtime access boundary
composition
→ создаёт несколько domain runtimes
→ передаёт готовые capabilities
```
Внутренняя assembly одного domain и cross-domain graph разделены правилами [Client и server assembly](./layers/domains/client-and-server.md) и [Compositions](./layers/compositions.md).

View File

@@ -0,0 +1,39 @@
---
title: Основные инварианты
status: draft
normative: true
---
# Основные инварианты
SLM Design организует frontend-приложение по владельцам ответственности. Архитектурная единица определяется не типом файла, а тем, кто владеет моделью, поведением, данными, runtime и lifecycle.
## Ответственность до размещения
**SLM-FND-001 - ОБЯЗАН.** Перед размещением кода необходимо определить его владельца, public API, runtime-зависимости и lifecycle scope.
**SLM-FND-002 - СЛЕДУЕТ.** Код следует размещать в минимальном scope, который полностью владеет его ответственностью.
**SLM-FND-003 - ЗАПРЕЩЕНО.** Нельзя переносить код в общий слой или общий package только на основании предполагаемого будущего переиспользования.
## Путь продуктовых данных
Внешний сервис может оставаться физическим источником данных. Domain business является единственным публичным шлюзом доменной истины внутри приложения. Точные требования определены правилами [SLM-DATA-001 - SLM-DATA-003](./state-and-data.md#domain-gateway) и [SLM-BUS-017 - SLM-BUS-020](./layers/domains/business.md#normalization-и-errors).
## Явные зависимости
**SLM-FND-007 - ОБЯЗАН.** Runtime-возможности должны поступать владельцу поведения через явные contracts, а не через скрытые imports, service locator или global mutable state.
**SLM-FND-008 - ЗАПРЕЩЕНО.** Type cast, barrel, alias, dynamic import или helper в `shared` не могут использоваться для обхода архитектурной границы.
## Public API
Межмодульное взаимодействие и deep imports регулируются [SLM-API-001 - SLM-API-005](./public-api-and-imports.md#общие-правила).
## Scope и lifecycle
Создание, scope, activation и cleanup runtime определены в [Runtime и lifecycle](./runtime-and-lifecycle.md).
## Композиция доменов
Cross-domain runtime graph регулируется [SLM-CMP-001 - SLM-CMP-005](./layers/compositions.md#cross-domain-graph) и [Cross-domain boundary](./layers/domains/cross-domain-boundary.md).

View File

@@ -0,0 +1,73 @@
---
title: SLM Design Specification
version: 0.1.0-draft
status: draft
normative: true
---
# SLM Design Specification
Эта директория содержит единый нормативный корпус SLM Design 2.0. Спецификация разделена на главы, но имеет общую версию, общий статус и единый приоритет правил.
Пока статус равен `draft`, документы описывают проектируемую архитектуру и не заменяют действующую документацию в `docs/`.
## Нормативный язык
| Термин | Значение |
|---|---|
| `ОБЯЗАН` | Требование необходимо выполнить для соответствия спецификации |
| `ЗАПРЕЩЕНО` | Действие является нарушением спецификации |
| `СЛЕДУЕТ` | Рекомендуемое решение; отступление требует явного обоснования |
| `МОЖЕТ` | Допустимый, но необязательный вариант |
Правила имеют стабильные идентификаторы вида `SLM-AREA-NNN`. Точное нормативное требование принадлежит только той главе, где объявлен его rule ID. Обзорная глава может ссылаться на правило, но не должна объявлять его повторно под новым ID.
## Приоритет
**SLM-DOC-001 - ОБЯЗАН.** При конфликте между главами спецификации и любым ненормативным материалом приоритет имеет спецификация.
**SLM-DOC-002 - ЗАПРЕЩЕНО.** Ненормативный документ не может вводить новое обязательное правило, исключение или архитектурную границу.
**SLM-DOC-003 - ОБЯЗАН.** Изменение принятого архитектурного правила должно вноситься в главу, которая владеет соответствующим rule ID.
## Главы
### Основы
- [Основные инварианты](./foundations.md)
- [Терминология](./terminology.md)
- [Архитектурная модель](./architecture-model.md)
### Слои
- [Обзор слоёв](./layers/index.md)
- [App](./layers/app.md)
- [Compositions](./layers/compositions.md)
- [Domains](./layers/domains/index.md)
- [Infra](./layers/infra.md)
- [UI](./layers/ui.md)
- [Shared](./layers/shared.md)
### Внутренняя модель Domains
- [Business](./layers/domains/business.md)
- [Framework surface](./layers/domains/framework.md)
- [Ports и adapters](./layers/domains/ports-and-adapters.md)
- [Client и server assembly](./layers/domains/client-and-server.md)
- [Cross-domain boundary](./layers/domains/cross-domain-boundary.md)
### Общие правила
- [Модули и группы](./modules-and-groups.md)
- [Сегменты](./segments.md)
- [Public API и импорты](./public-api-and-imports.md)
- [State и data](./state-and-data.md)
- [Runtime и lifecycle](./runtime-and-lifecycle.md)
- [Тестирование и соответствие](./testing-and-conformance.md)
- [Монорепозитории](./monorepo.md)
## Область текущего draft
Спецификация фиксирует уже согласованные границы слоёв, доменов, business-фабрик, adapters, runtime assembly и cross-domain composition.
Точная форма React Providers, окончательная политика package extraction для domains и единая модель query cache не фиксируются сверх явно объявленных в соответствующих главах инвариантов.

View File

@@ -0,0 +1,70 @@
---
title: Слой App
status: draft
normative: true
---
# Слой App
`app` является boundary между framework routing/runtime и SLM-модулями приложения.
## Ответственность
`app` может содержать:
- route files;
- framework layout/error/loading/not-found entries;
- framework metadata и route parameters;
- bootstrap imports;
- подключение global styles/assets;
- framework-required middleware и handlers.
## Правила
**SLM-APP-001 - ОБЯЗАН.** Route entry должен оставаться тонким adapter, нормализующим framework input и делегирующим готовому composition module.
```text
framework route
→ composition entry
```
**SLM-APP-002 - ЗАПРЕЩЕНО.** `app` не может владеть product page, screen, widget, domain scenario, store, domain Provider или cross-domain graph.
**SLM-APP-003 - ЗАПРЕЩЕНО.** Route entry не должен напрямую собирать domain adapters, вызывать SDK или формировать product model.
**SLM-APP-004 - ОБЯЗАН.** Framework-specific input должен быть считан в `app` и передан вниз в минимальной нормализованной форме.
Механическая нормализация включает извлечение route params, headers и framework wrappers. Product validation, создание value objects и выбор domain outcome остаются в domain business.
**SLM-APP-005 - ЗАПРЕЩЕНО.** Другие SLM-слои не могут импортировать `app`.
**SLM-APP-006 - СЛЕДУЕТ.** Framework behavior, которому нужен product graph или product UI, следует реализовать готовым composition entry и только подключить из `app`.
**SLM-APP-007 - МОЖЕТ.** `app` может напрямую импортировать framework APIs и static/global resources из `shared`, если framework требует подключить их в root entry.
## Допустимая структура
Структуру `app` определяет framework. SLM не требует превращать framework directories в SLM modules и не требует `index.ts` для route folders.
```text
app/
├── layout.tsx
├── error.tsx
├── not-found.tsx
├── api/
└── products/
└── [product]/
└── page.tsx
```
## Недопустимые владельцы
Следующие сущности не должны определяться в `app`:
- `ProductPage`;
- `AuthProvider`;
- `createOrdersRuntime`;
- page-local store;
- domain mapper;
- reusable product component;
- concrete product adapter.

View File

@@ -0,0 +1,99 @@
---
title: Слой Compositions
status: draft
normative: true
---
# Слой Compositions
`compositions` собирает application flows из готовых domain runtimes, infra capabilities, UI modules и других composition modules.
## Ответственность
Composition может быть:
- page;
- route composition entry;
- layout;
- screen;
- widget;
- provider composition;
- multi-domain hook;
- non-visual graph owner.
Структура слоя свободна и должна отражать продуктовую навигацию приложения.
```text
compositions/
├── pages/
├── layouts/
├── screens/
├── widgets/
└── providers/
```
Эти папки являются groups, а не отдельными слоями.
## Cross-domain graph
**SLM-CMP-001 - ОБЯЗАН.** Runtime graph нескольких domains должен собираться в composition, которая владеет его scope.
```ts
const auth = createAuthRuntime()
const user = createUserRuntime({ auth: auth.session })
const orders = createOrdersRuntime({ user: user.agreements })
```
**SLM-CMP-002 - ОБЯЗАН.** Composition должна создавать domain runtimes в явном ацикличном порядке.
**SLM-CMP-003 - ОБЯЗАН.** Cross-domain dependency должна передаваться как готовая минимальная capability, а не разрешаться service locator или domain import.
**SLM-CMP-004 - ЗАПРЕЩЕНО.** Composition не может повторять adapter wiring, если domain public assembly уже создаёт готовый runtime.
**SLM-CMP-005 - ЗАПРЕЩЕНО.** Composition не должна импортировать private business services, domain adapters, SDK-specific domain integration или внутренний Context domain.
**SLM-CMP-013 - ОБЯЗАН.** Composition должна использовать public client/server creator domain, если domain предоставляет runtime-specific assembly.
**SLM-CMP-014 - МОЖЕТ.** Composition может вызвать public business factory напрямую только для полностью universal domain без concrete adapters и runtime-specific assembly.
## Product UI
**SLM-CMP-006 - ОБЯЗАН.** UI, объединяющий несколько domains, route/page scope или application flow, принадлежит `compositions`.
Примеры:
- header, объединяющий auth, cart и navigation;
- order flow, который требует auth и user agreements;
- page screen;
- route guard с navigation outcome;
- widget, использующий hooks двух domains.
**SLM-CMP-007 - МОЖЕТ.** Composition может использовать domain UI и universal UI, передавать им props, callbacks и slots.
## State
**SLM-CMP-008 - ОБЯЗАН.** Page-local presentation state принадлежит минимальной composition, охватывающей всех его consumers.
Примеры page-local state:
- открытие sidebar;
- активная вкладка;
- route-local wizard step;
- presentation filters;
- состояние раскрытия section.
**SLM-CMP-009 - ЗАПРЕЩЕНО.** Page store не может становиться параллельным владельцем domain model или product cache.
## Imports
**SLM-CMP-010 - МОЖЕТ.** Composition module может импортировать public API других composition modules, domains, infra, ui и shared.
**SLM-CMP-011 - ЗАПРЕЩЕНО.** Runtime-циклы между composition modules запрещены.
**SLM-CMP-012 - ОБЯЗАН.** App-specific graph type должен отражать только реально предоставленные runtimes; `Partial<Graph>` с последующим приведением к полному graph запрещён.
**SLM-CMP-015 - ОБЯЗАН.** Client и server composition entries должны иметь раздельные public entrypoints и environment markers, если composition участвует в обоих runtime graphs.
## Scope
Composition может владеть application, route, page, request или test scope. Выбор scope должен следовать правилам [runtime и lifecycle](../runtime-and-lifecycle.md).

View File

@@ -0,0 +1,128 @@
---
title: Business domain
status: draft
normative: true
---
# Business
`business` является framework-neutral зоной domain и единственным владельцем его продуктовой semantics.
## Структура
```text
domains/{group...}/{domain}/business/
├── {domain}.factory.ts
├── index.ts
├── types/
├── ports/
├── services/
├── errors/
├── mappers/
├── selectors/
├── validators/
└── lib/
```
Конкретный набор внутренних segments определяется размером domain. Обязательны роль factory и public boundary, но не каждая папка из примера.
## Factory boundary
**SLM-BUS-001 - ОБЯЗАН.** Business должен создавать public runtime API через factory `{domain}Factory`.
**SLM-BUS-002 - ОБЯЗАН.** Factory должна принимать все runtime capabilities через business-owned dependency contracts.
**SLM-BUS-003 - ОБЯЗАН.** Factory должна возвращать framework-neutral DomainRuntime или logic API domain.
**SLM-BUS-004 - ЗАПРЕЩЕНО.** Factory не может возвращать React hooks, components, Providers, layouts, route guards или framework boundaries.
**SLM-BUS-005 - ЗАПРЕЩЕНО.** Factory constructor не может выполнять I/O, открывать socket, регистрировать subscription, запускать timer или читать hidden environment.
## Public API
**SLM-BUS-006 - ОБЯЗАН.** `business/index.ts` должен экспортировать единственное runtime value: factory.
**SLM-BUS-007 - МОЖЕТ.** `business/index.ts` может экспортировать business-owned types через `export type`.
```ts
export { authFactory } from './auth.factory'
export type {
AuthDeps,
AuthFactory,
AuthRuntime,
AuthState,
} from './types'
```
**SLM-BUS-008 - ЗАПРЕЩЕНО.** Error classes, error guards, error code constants, selectors, validators, formatters, services, mappers и port implementations не экспортируются как отдельные runtime values.
Если внешнему consumer нужна такая capability, она должна быть осмысленной частью factory runtime API, а не обходным direct export.
## Runtime API
DomainRuntime может предоставлять:
- commands;
- imperative queries;
- snapshots;
- subscriptions;
- selectors через стабильные methods;
- validation operations;
- typed outcomes;
- explicit lifecycle operations.
**SLM-BUS-009 - ОБЯЗАН.** Runtime API должен говорить на языке domain и не повторять endpoint names, SDK tree или storage schema.
**SLM-BUS-010 - ЗАПРЕЩЕНО.** Public contract не может раскрывать generated DTO, SDK client, query-library result, concrete store API, raw Context или adapter.
## Dependencies и ports
**SLM-BUS-011 - ОБЯЗАН.** Business-owned dependency описывает минимальную внешнюю возможность на языке domain.
```ts
export type AuthPhonePort = {
requestCode: (phone: string) => Promise<unknown>
verifyCode: (input: VerifyPhoneCodeInput) => Promise<unknown>
}
```
**SLM-BUS-012 - ОБЯЗАН.** Ненадёжный внешний результат должен приниматься как `unknown`, если business обязан проверить его runtime-форму.
**SLM-BUS-013 - ЗАПРЕЩЕНО.** Business dependency не может быть generated DTO, полный SDK client, `StoreApi`, QueryClient или framework hook.
**SLM-BUS-014 - ОБЯЗАН.** Subscription port должен предоставлять cleanup contract.
## Imports
Business может runtime-импортировать:
- собственные файлы;
- детерминированный `shared`;
- pure libraries без I/O, hidden state и public type leakage.
Business может type-only импортировать стабильный public contract другого domain, если dependency невозможно корректно описать локальным port. Локальный consumer-owned port является предпочтительным вариантом.
**SLM-BUS-015 - ЗАПРЕЩЕНО.** Business не импортирует React, query runtime, state manager, SDK, generated operation, HTTP client, storage implementation, browser API, infra, composition или assembly.
Cross-domain imports дополнительно регулируются [SLM-XDOM-001 и SLM-XDOM-005 - SLM-XDOM-008](./cross-domain-boundary.md).
## Normalization и errors
**SLM-BUS-017 - ОБЯЗАН.** External result должен быть нормализован в business-owned model до выхода из DomainRuntime.
**SLM-BUS-018 - ОБЯЗАН.** Malformed successful response должен считаться нарушением runtime contract, а не валидным отсутствием данных.
**SLM-BUS-019 - ОБЯЗАН.** Expected domain outcome и technical failure должны быть различимы в public contract.
**SLM-BUS-020 - ЗАПРЕЩЕНО.** Source error, HTTP status, SDK error class, raw response и transport message не могут быть consumer contract.
Business может выражать ожидаемые outcomes через typed result или domain error. Эта draft-версия не предписывает единственную форму обработки ожидаемых ошибок, но требует business-owned semantics и стабильных discriminants.
## State
**SLM-BUS-021 - ОБЯЗАН.** Business владеет domain state model, допустимыми transitions и semantics commands/selectors.
**SLM-BUS-022 - ЗАПРЕЩЕНО.** Business не импортирует concrete store implementation.
Framework-neutral state runtime может быть создан самой factory или предоставлен через business-owned port. Выбор не должен раскрывать concrete implementation в public API.

View File

@@ -0,0 +1,93 @@
---
title: Client и server assembly domain
status: draft
normative: true
---
# Client и Server Assembly
`client` и `server` создают готовые runtime-specific instances одного domain поверх его business factory и adapters.
## Client assembly
```text
domains/{group...}/{domain}/client/
├── create-{domain}-client-runtime.ts
└── index.ts
```
```ts
export const createAuthClientRuntime = (): AuthRuntime => {
return authFactory({
phoneAuth: browserPhoneAuthAdapter,
session: browserSessionAdapter,
})
}
```
**SLM-ASM-001 - ОБЯЗАН.** Client assembly может импортировать только собственную business factory, собственные client adapters, собственную React surface и необходимые runtime-specific technical inputs.
**SLM-ASM-002 - ОБЯЗАН.** Client assembly должна возвращать готовый runtime собственного domain.
**SLM-ASM-003 - ЗАПРЕЩЕНО.** Client assembly одного domain не может импортировать creator или runtime другого domain.
**SLM-ASM-004 - МОЖЕТ.** Client assembly может принимать готовую внешнюю capability через собственный input contract.
```ts
createUserClientRuntime({ auth: auth.session })
```
Такой input не даёт user domain права создавать AuthRuntime или импортировать его client entrypoint.
## Server assembly
```text
domains/{group...}/{domain}/server/
├── create-{domain}-server-runtime.ts
└── index.ts
```
**SLM-ASM-005 - ОБЯЗАН.** Server assembly должна создавать новый runtime в scope, соответствующем request или другой явно выбранной server lifetime.
**SLM-ASM-006 - ЗАПРЕЩЕНО.** Request credentials, cookies, headers и user-specific state не могут сохраняться в process-level mutable singleton.
**SLM-ASM-007 - ОБЯЗАН.** Framework/request input используется только для создания server adapters и не протекает как raw framework object в business API.
**SLM-ASM-008 - ОБЯЗАН.** Server entrypoint должен иметь явный server-only marker, если framework предоставляет такой механизм.
**SLM-ASM-015 - МОЖЕТ.** Server assembly может принимать готовую внешнюю capability через собственный input contract на тех же условиях, что и client assembly.
**SLM-ASM-016 - ОБЯЗАН.** Server assembly может импортировать только собственную business factory, собственные server adapters и необходимые server technical inputs; импорт React/client surface запрещён.
## Constructor и activation
Assembly определяет способ создания, но не владеет полным cross-domain graph.
**SLM-ASM-009 - ЗАПРЕЩЕНО.** Вызов runtime creator не должен выполнять product request, открывать socket или запускать background resource.
```text
module import
→ определяет creator
creator call
→ создаёт runtime instance
explicit start
→ запускает resources
```
**SLM-ASM-010 - ОБЯЗАН.** Resources запускает graph owner в выбранном scope согласно [lifecycle rules](../../runtime-and-lifecycle.md).
## Public entrypoints
**SLM-ASM-011 - ОБЯЗАН.** Client и server assembly должны иметь разные public entrypoints.
**SLM-ASM-012 - ЗАПРЕЩЕНО.** Общий domain barrel не может runtime-реэкспортировать одновременно client и server surfaces.
## Server/client bridge
Client и server runtimes являются разными instances над общей business semantics.
**SLM-ASM-013 - ЗАПРЕЩЕНО.** DomainRuntime, functions, Context, store или query client нельзя передавать через serializable server/client boundary.
**SLM-ASM-014 - МОЖЕТ.** Server может передать client assembly только serializable business-owned bootstrap data без secrets и mutable runtime objects.

View File

@@ -0,0 +1,103 @@
---
title: Cross-domain boundary
status: draft
normative: true
---
# Cross-domain Boundary
Domains не образуют скрытый runtime graph внутри слоя `domains`. Граф связывается только graph owner в `compositions`.
## Runtime imports
**SLM-XDOM-001 - ЗАПРЕЩЕНО.** `business` domain A не импортирует runtime values domain B.
**SLM-XDOM-002 - ЗАПРЕЩЕНО.** Framework surface domain A не импортирует hooks, Provider, Context, components или runtime domain B.
**SLM-XDOM-003 - ЗАПРЕЩЕНО.** Adapter domain A не импортирует adapter или runtime domain B.
**SLM-XDOM-004 - ЗАПРЕЩЕНО.** Client/server assembly domain A не импортирует runtime creator domain B.
Запрет распространяется на direct import, barrel re-export, dynamic import, lazy import и service locator resolution.
## Type-only contracts
**SLM-XDOM-005 - МОЖЕТ.** Business и client/server input contracts domain могут type-only импортировать минимальный стабильный business contract другого domain.
**SLM-XDOM-006 - СЛЕДУЕТ.** Зависимому domain следует объявлять consumer-owned port, если capability можно описать без зависимости от полного foreign API.
```ts
export type UserAuthPort = {
getSessionSnapshot: () => SessionSnapshot
subscribeToSession: (listener: () => void) => () => void
}
```
Type-only import не разрешает runtime import и не переносит ownership.
**SLM-XDOM-012 - ЗАПРЕЩЕНО.** Type dependency cycle между domains запрещён, даже если не создаёт runtime cycle.
## Runtime capability injection
**SLM-XDOM-007 - МОЖЕТ.** Domain runtime creator может принять готовую structurally compatible capability, созданную другим domain и переданную composition.
```ts
const auth = createAuthClientRuntime()
const user = createUserClientRuntime({ auth: auth.session })
```
User domain знает только свой input contract. Он не знает creator, Provider, adapters и scope AuthRuntime.
**SLM-XDOM-008 - ОБЯЗАН.** Передаваемая capability должна быть минимальной и не раскрывать raw store, Context, SDK client или mutable internals foreign domain.
**SLM-XDOM-013 - МОЖЕТ.** Structurally compatible foreign capability может реализовать consumer-owned port напрямую. Wrapper adapter создаётся только при необходимости преобразовать contracts или lifecycle.
## React composition
Если React-сущность использует runtime API двух domains, она принадлежит `compositions`.
```tsx
const ProtectedOrderForm = () => {
const auth = useAuth()
const order = useOrder()
return auth.isAuthenticated
? <OrderForm order={order} />
: <AuthPrompt />
}
```
**SLM-XDOM-009 - ОБЯЗАН.** Domain UI может получать от composition только domain-local или presentation-neutral props, callbacks и slots. Foreign domain semantics остаётся во владеющей composition.
```tsx
<AuthRequired>
<OrderForm />
</AuthRequired>
```
Такое связывание выполняется в composition, а не внутри auth или orders.
## Events
**SLM-XDOM-010 - ЗАПРЕЩЕНО.** Domain не подписывается напрямую на event emitter другого domain через runtime import.
Composition может передать event capability через consumer-owned port:
```ts
const orders = createOrdersClientRuntime({
userEvents: {
subscribeToIdentity: user.identity.subscribe,
},
})
```
## Cycles
**SLM-XDOM-011 - ЗАПРЕЩЕНО.** Runtime dependency cycle между domains является нарушением границы и не может скрываться event bus, lazy resolution или two-way service locator.
Ненормативное пояснение: при обнаружении цикла следует пересмотреть один из вариантов:
- пересмотреть границы domains;
- перенести orchestration в composition;
- выделить отдельную product responsibility;
- инвертировать зависимость через consumer-owned port.

View File

@@ -0,0 +1,75 @@
---
title: Framework surface domain
status: draft
normative: true
---
# Framework Surface
Framework surface адаптирует готовый DomainRuntime к execution model конкретного framework. В текущей структуре React surface располагается в `react/`.
## Структура React surface
```text
domains/{group...}/{domain}/react/
├── context/
├── providers/
├── hooks/
├── ui/
└── index.ts
```
Ни один segment не обязателен без реальной потребности.
## Runtime access
**SLM-FRM-001 - ОБЯЗАН.** Framework surface должна работать с конкретным DomainRuntime через domain-owned runtime access boundary.
**SLM-FRM-002 - ЗАПРЕЩЕНО.** Framework hook или component не может самостоятельно вызывать business factory, создавать adapters или разрешать runtime из global service locator.
**SLM-FRM-003 - ОБЯЗАН.** Runtime access boundary должна получать готовый DomainRuntime извне и не создавать параллельное domain state.
Для React типичным механизмом является private Context, связывающий статически экспортированные hooks/components с переданным runtime instance. Это пояснение не предписывает точную форму или количество Providers в текущем draft.
## Imports
**SLM-FRM-004 - ОБЯЗАН.** React surface может импортировать business runtime contracts только через `import type`.
**SLM-FRM-005 - ЗАПРЕЩЕНО.** React surface не может runtime-импортировать business factory, private business services, selectors, validators, errors или constants.
**SLM-FRM-006 - ЗАПРЕЩЕНО.** React surface не может импортировать domain adapters, SDK, product infra client или assembly.
**SLM-FRM-007 - МОЖЕТ.** React surface может импортировать public API `ui`, `shared` и framework libraries, разрешённые её runtime profile.
**SLM-FRM-008 - ЗАПРЕЩЕНО.** React surface одного domain не импортирует runtime surface другого domain.
## Hooks
**SLM-FRM-009 - ОБЯЗАН.** Domain hook должен получать product data и behavior только через текущий DomainRuntime.
**SLM-FRM-010 - МОЖЕТ.** Hook может использовать framework query/cache runtime как private implementation поверх imperative DomainRuntime query.
**SLM-FRM-011 - ЗАПРЕЩЕНО.** Query hook не может использовать adapter или SDK call как fetcher в обход DomainRuntime.
**SLM-FRM-012 - ЗАПРЕЩЕНО.** Query-library types, cache keys и raw mutate API не могут становиться public business contract.
## Domain UI
Domain React UI может:
- вызывать hooks своего domain;
- использовать universal UI;
- отображать domain-owned states и outcomes;
- принимать callbacks, props и slots от composition.
**SLM-FRM-013 - ЗАПРЕЩЕНО.** Domain UI не может импортировать runtime другого domain или оркестрировать route/page flow.
Владение React UI, использующим несколько domains, определено правилом [SLM-CMP-006](../compositions.md#product-ui).
## Client boundary
**SLM-FRM-015 - ОБЯЗАН.** Entry point React hooks, Context и interactive UI должен быть явно отмечен как client runtime согласно правилам используемого framework.
**SLM-FRM-016 - ЗАПРЕЩЕНО.** Server-compatible React export не может попадать в client entrypoint только из-за нахождения рядом с client hooks или Provider.
React не является синонимом client runtime; environment profile определяется фактическими dependencies export.

View File

@@ -0,0 +1,91 @@
---
title: Слой Domains
status: draft
normative: true
---
# Слой Domains
`domains` содержит законченные вертикальные продуктовые модули. Domain объединяет business logic, framework surfaces, concrete adapters и runtime-specific assembly одной предметной ответственности, не смешивая их внутренние направления зависимостей.
## Domain и group
**SLM-DOM-001 - ОБЯЗАН.** Конечный domain должен располагаться непосредственно в `domains` или внутри одной или нескольких навигационных groups.
```text
domains/{domain}
domains/{group}/{domain}
domains/{group}/{nested-group}/{domain}
```
**SLM-DOM-002 - ЗАПРЕЩЕНО.** Domain group не может иметь `index.ts`, public API, state, adapters, assembly или runtime.
```text
domains/
├── navigation/ # domain
└── knv/ # group
├── auth/ # domain
├── user/ # domain
└── orders/ # domain
```
**SLM-DOM-003 - ОБЯЗАН.** Первой архитектурной единицей в group tree является конечная папка, владеющая самостоятельной предметной ответственностью.
## Внутренние зоны
Базовая форма domain:
```text
domains/{group...}/{domain}/
├── business/
├── react/
├── adapters/
├── client/
└── server/
```
| Зона | Статус | Ответственность |
|---|---|---|
| [`business`](./business.md) | Обязательная | Domain model, factory, ports, scenarios, errors |
| [`react`](./framework.md) | Опциональная | React runtime access, hooks, Providers, domain UI |
| [`adapters`](./ports-and-adapters.md) | Опциональная | Concrete реализации business-owned ports |
| [`client`](./client-and-server.md) | Опциональная | Browser/client assembly одного domain |
| [`server`](./client-and-server.md) | Опциональная | Server/request assembly одного domain |
**SLM-DOM-004 - ОБЯЗАН.** Каждый domain должен содержать `business` как единственный владелец product model и business semantics.
**SLM-DOM-005 - СЛЕДУЕТ.** Опциональную зону следует добавлять только при наличии реального runtime consumer и самостоятельной ответственности.
**SLM-DOM-006 - ЗАПРЕЩЕНО.** Нельзя создавать пустые симметричные `react`, `adapters`, `client` или `server` на будущее.
**SLM-DOM-007 - ОБЯЗАН.** Domain zones должны соблюдать внутреннюю dependency direction, даже если физически находятся под одним владельцем.
## Domain ownership
Domain может владеть:
- model и value objects;
- product scenarios;
- domain state и transitions;
- ports;
- normalization и domain errors;
- framework hooks и UI одного domain;
- concrete integrations собственных ports;
- client/server runtime assembly собственного business.
Domain не владеет:
- page/route/layout composition;
- UI, объединяющим несколько domains;
- cross-domain graph;
- framework route entry;
- универсальным technical service;
- product-agnostic UI primitive.
## Product gateway
Framework surface и runtime assembly сохраняют business runtime единственным product gateway согласно [SLM-DATA-001 - SLM-DATA-003](../../state-and-data.md#domain-gateway).
## Cross-domain boundary
Domain может принять готовую внешнюю capability через contract, но не импортирует runtime surface другого domain. Точные правила определены в [cross-domain boundary](./cross-domain-boundary.md).

View File

@@ -0,0 +1,88 @@
---
title: Ports и adapters domain
status: draft
normative: true
---
# Ports и Adapters
Port определяет потребность business. Adapter связывает эту потребность с concrete runtime.
## Ownership
```text
domain/
├── business/
│ └── ports/
└── adapters/
```
**SLM-ADP-001 - ОБЯЗАН.** Port должен принадлежать `business` того domain, который потребляет capability.
**SLM-ADP-002 - ОБЯЗАН.** Concrete adapter должен принадлежать тому же domain, но находиться вне `business`.
**SLM-ADP-003 - ЗАПРЕЩЕНО.** Infra или external SDK не могут объявлять business port от имени domain.
## Adapter contract
**SLM-ADP-004 - МОЖЕТ.** Adapter может выполнять только следующие integration responsibilities:
- импортировать type-only business port и domain input types;
- импортировать public infra API, SDK или platform runtime;
- переводить domain arguments в transport arguments;
- возвращать raw/unknown source result для business normalization;
- подписываться на concrete event source через явный lifecycle contract.
**SLM-ADP-005 - ЗАПРЕЩЕНО.** Adapter не может выполнять следующие domain/framework responsibilities:
- создавать domain error;
- выбирать domain fallback;
- реализовывать business rule;
- объявлять domain model;
- экспортировать concrete client consumer-коду;
- вызывать framework hook;
- обращаться к другому domain runtime.
**SLM-ADP-006 - ОБЯЗАН.** Adapter должен реализовывать ровно тот port contract, который необходим business.
**SLM-ADP-007 - ЗАПРЕЩЕНО.** Нельзя передавать полный client, если port требует ограниченный набор capabilities.
**SLM-ADP-008 - ЗАПРЕЩЕНО.** Adapter integration logic не должна писаться inline в composition или runtime assembly.
## Client и server adapters
Adapters могут быть разделены по runtime:
```text
adapters/
├── client/
│ ├── browser-session.adapter.ts
│ └── websocket-orders.adapter.ts
└── server/
├── request-session.adapter.ts
└── server-orders-api.adapter.ts
```
**SLM-ADP-009 - ОБЯЗАН.** Client adapter не должен попадать в server graph, а server adapter - в client graph.
**SLM-ADP-010 - ОБЯЗАН.** Runtime-specific adapter должен иметь явный environment marker, если framework предоставляет такой механизм.
## Event sources
Socket, subscription и event listener реализуют event port:
```ts
export type OrdersEventsPort = {
subscribe: (listener: (event: unknown) => void) => () => void
}
```
**SLM-ADP-011 - ОБЯЗАН.** Event adapter должен возвращать cleanup и не открывать connection при module import.
**SLM-ADP-012 - ОБЯЗАН.** Wire event проходит business normalization до изменения domain state или передачи consumer-коду.
## Public boundary
**SLM-ADP-013 - ЗАПРЕЩЕНО.** `adapters` не имеет внешнего public API для app, compositions или других domains.
Adapters доступны только assembly собственного domain и собственным contract tests.

View File

@@ -0,0 +1,43 @@
---
title: Слои
status: draft
normative: true
---
# Слои
Слой определяет вид ответственности, допустимые зависимости и типы modules внутри верхнеуровневой папки `src`.
## Матрица ответственности
| Слой | Владеет | Не владеет |
|---|---|---|
| [`app`](./app.md) | Framework routes, bootstrap, глобальные framework boundaries | Product UI, domain logic, page state, graph assembly |
| [`compositions`](./compositions.md) | Pages, layouts, screens, widgets, cross-domain graph, scope | Domain model, domain adapters, universal UI primitives |
| [`domains`](./domains/index.md) | Product model, scenarios, ports, adapters, runtime surfaces | Route/page composition и UI нескольких domains |
| [`infra`](./infra.md) | Technical services, transports, platform integrations | Product semantics и domain graph |
| [`ui`](./ui.md) | Product-agnostic UI modules | Product scenarios и data sources |
| [`shared`](./shared.md) | Детерминированные общие resources | Runtime state, I/O и product knowledge |
## Общие правила
**SLM-LAY-001 - ОБЯЗАН.** Модуль должен располагаться в слое, который владеет его основной ответственностью.
**SLM-LAY-002 - ЗАПРЕЩЕНО.** Нельзя выбирать слой по техническому типу файла без определения владельца поведения и данных.
**SLM-LAY-003 - ОБЯЗАН.** Межслойный import должен одновременно соответствовать общей dependency direction и public API импортируемого module.
**SLM-LAY-004 - ЗАПРЕЩЕНО.** Нельзя создавать proxy module в разрешённом слое только для обхода запрещённого направления import.
**SLM-LAY-005 - СЛЕДУЕТ.** При смешанной ответственности module следует разделить по реальным владельцам. Cross-module и cross-domain orchestration следует выполнить в `compositions`; связь business с собственными adapters выполняется assembly соответствующего domain.
## Выбор слоя
| Вопрос | Слой |
|---|---|
| Код существует только из-за framework route/bootstrap? | `app` |
| Код собирает page, route, несколько modules или domains? | `compositions` |
| Код выражает продуктовую модель, сценарий или domain UI? | `domains` |
| Код предоставляет техническую capability приложения? | `infra` |
| Компонент не содержит product semantics и сценария? | `ui` |
| Код детерминирован, не знает продукт и не имеет runtime state? | `shared` |

View File

@@ -0,0 +1,61 @@
---
title: Слой Infra
status: draft
normative: true
---
# Слой Infra
`infra` содержит технические capabilities приложения, не определяющие продуктовую модель и сценарии.
## Примеры modules
```text
infra/
├── http/
├── backend-api/
├── realtime/
├── analytics/
├── logger/
├── app-config/
├── storage/
├── i18n/
└── theme/
```
## Правила
**SLM-INF-001 - ОБЯЗАН.** Infra module должен описывать техническую capability, а не продуктовый domain.
**SLM-INF-002 - МОЖЕТ.** Infra module может импортировать public API другого infra module и `shared`.
**SLM-INF-003 - ЗАПРЕЩЕНО.** Infra module не может импортировать `domains`, `compositions` или `app`.
**SLM-INF-004 - ЗАПРЕЩЕНО.** Infra не может собирать domain factory, хранить cross-domain graph или предоставлять generic product service locator.
**SLM-INF-005 - ЗАПРЕЩЕНО.** Infra не создаёт domain errors, domain fallback и domain model из transport DTO.
**SLM-INF-006 - МОЖЕТ.** Infra может экспортировать technical client, transport, event source, storage primitive или platform wrapper через собственный public API.
**SLM-INF-007 - ОБЯЗАН.** Generated SDK и transport details должны оставаться внутри infra или concrete domain adapter и не становиться public contract продуктовых consumers.
## Отличие от adapter
Infra знает технический механизм:
```text
HTTP client
WebSocket transport
local storage primitive
analytics SDK
```
Domain adapter знает, какая часть этого механизма реализует конкретный business-owned port:
```text
AuthPhonePort
OrdersEventsPort
UserAgreementsStoragePort
```
Один infra module может использоваться adapters нескольких domains без знания их product semantics.

View File

@@ -0,0 +1,42 @@
---
title: Слой Shared
status: draft
normative: true
---
# Слой Shared
`shared` является детерминированным фундаментом приложения и не знает о SLM-модулях верхних слоёв.
## Допустимое содержимое
- pure utilities;
- value predicates;
- product-agnostic types;
- styling foundation и tokens;
- static resources;
- compile-time constants без product ownership;
- deterministic formatting primitives.
## Правила
**SLM-SHR-001 - ОБЯЗАН.** Результат shared utility должен определяться явными аргументами и не зависеть от скрытого runtime environment.
**SLM-SHR-002 - ЗАПРЕЩЕНО.** `shared` не может импортировать `app`, `compositions`, `domains`, `infra` или `ui`.
**SLM-SHR-003 - ЗАПРЕЩЕНО.** `shared` не может владеть product types, domain rules, runtime state, I/O, storage access или event subscriptions.
**SLM-SHR-004 - ЗАПРЕЩЕНО.** Нельзя переносить domain helper, DTO, adapter contract или product config в `shared` для обхода import boundary.
**SLM-SHR-005 - СЛЕДУЕТ.** Код следует поднимать в `shared` только при подтверждённой product-agnostic semantics, а не из-за повторения нескольких строк.
## Отличие от других слоёв
| Код | Владелец |
|---|---|
| Domain email validator с product rules | `domains/{domain}/business` |
| Generic string trim utility | `shared` |
| Browser storage wrapper | `infra` |
| Domain storage adapter | `domains/{domain}/adapters` |
| UI spacing tokens | `shared` |
| Button consuming spacing tokens | `ui` |

View File

@@ -0,0 +1,48 @@
---
title: Слой UI
status: draft
normative: true
---
# Слой UI
`ui` содержит reusable presentation modules без product scenario и domain ownership.
## Примеры
```text
ui/
├── button/
├── input/
├── icon/
├── modal/
├── carousel/
├── tabs/
└── tooltip/
```
## Правила
**SLM-UI-001 - ОБЯЗАН.** UI module должен быть применим без знания конкретного product domain.
**SLM-UI-002 - ЗАПРЕЩЕНО.** UI module не может импортировать `domains`, `compositions`, `app` или product-specific infra.
**SLM-UI-003 - МОЖЕТ.** UI module может импортировать public API других UI modules и `shared`.
**SLM-UI-004 - ЗАПРЕЩЕНО.** UI module не выбирает product data source, не вызывает domain scenario и не владеет cross-domain behavior.
**SLM-UI-005 - МОЖЕТ.** UI module может владеть локальным interaction state, необходимым только для собственной presentation mechanics.
**SLM-UI-006 - ОБЯЗАН.** Компонент с product semantics должен принадлежать framework surface domain или composition, а не `ui`.
## Классификация
| Сущность | Владелец |
|---|---|
| `Button`, `Input`, `Modal` | `ui` |
| `LoginForm` одного auth domain | domain framework surface |
| Header с auth и navigation | `compositions` |
| Generic date picker | `ui` |
| Medication schedule | domain или composition согласно используемым domains |
Универсальность определяется отсутствием product knowledge, а не количеством текущих consumers.

View File

@@ -0,0 +1,86 @@
---
title: Модули и группы
status: draft
normative: true
---
# Модули и Группы
## Module
Module является минимальным самостоятельным владельцем ответственности и предоставляет public boundary внешнему коду.
**SLM-MOD-001 - ОБЯЗАН.** Module должен иметь одну сформулированную ответственность и одного архитектурного owner.
**SLM-MOD-002 - ОБЯЗАН.** Внешний consumer взаимодействует с module только через его public API.
**SLM-MOD-003 - СЛЕДУЕТ.** Module следует ограничивать только теми внутренними parts и segments, которые необходимы текущей ответственности.
Типичные modules:
- page, layout, screen или widget в `compositions`;
- конечный domain в `domains`;
- technical service в `infra`;
- reusable UI module в `ui`.
`app` содержит framework entries и не обязан организовываться как SLM modules. `shared` может содержать небольшие public units, но не runtime modules.
## Group
Group классифицирует modules и другие groups, но не владеет поведением.
**SLM-MOD-004 - ЗАПРЕЩЕНО.** Group не может иметь `index.ts`, public API, state, runtime, dependencies или assembly.
**SLM-MOD-005 - ЗАПРЕЩЕНО.** Внешний код не может импортировать group path.
**SLM-MOD-006 - МОЖЕТ.** Group может содержать другие groups и конечные modules.
```text
domains/
└── knv/ # group
├── auth/ # domain module
└── orders/ # domain module
```
```text
compositions/
└── pages/ # group
├── home/ # composition module
└── profile/ # composition module
```
## Domain zones
**SLM-MOD-007 - ОБЯЗАН.** `business`, `react`, `adapters`, `client` и `server` внутри конечного domain являются внутренними zones одного domain, а не самостоятельными верхнеуровневыми modules.
Zones могут иметь собственные entrypoints, но domain остаётся единым владельцем product responsibility.
## Component
Component является presentation unit внутри module и не считается самостоятельным архитектурным owner.
**SLM-MOD-008 - ЗАПРЕЩЕНО.** Component не может самостоятельно выбирать product source, собирать domain runtime или оркестрировать несколько modules.
**SLM-MOD-009 - МОЖЕТ.** Component может владеть локальной presentation mechanics и рендерить другие components, разрешённые слоем владельца.
**SLM-MOD-010 - ОБЯЗАН.** Presentation unit с самостоятельной ответственностью, внешними архитектурными dependencies или внутренней modular structure должна оформляться как module или nested module. Сам факт локального hook/state не делает component модулем.
## Nested module
Самостоятельная часть родительского module может быть оформлена nested module, если имеет собственную ответственность и public boundary только внутри родителя.
```text
compositions/pages/home/
└── parts/
└── hero-section/
├── hero-section.tsx
└── index.ts
```
**SLM-MOD-011 - ЗАПРЕЩЕНО.** Nested module не может использоваться для сокрытия ответственности, которой фактически владеет другой слой или domain.
## Scope evolution
**SLM-MOD-012 - СЛЕДУЕТ.** Код следует поднимать из локального owner в более широкий module только после появления реального совместного consumer или общей ответственности.
**SLM-MOD-013 - ЗАПРЕЩЕНО.** Физическое повторение само по себе не доказывает общий ownership.

View File

@@ -0,0 +1,61 @@
---
title: Монорепозитории
status: draft
normative: true
---
# Монорепозитории
SLM применяется внутри границы каждого frontend-приложения. Workspace packages имеют собственные public boundaries и ownership.
## Application boundary
```text
apps/
└── web/
└── src/
├── app/
├── compositions/
├── domains/
├── infra/
├── ui/
└── shared/
```
**SLM-MONO-001 - ОБЯЗАН.** Каждое приложение должно самостоятельно определять свой product graph и application compositions.
**SLM-MONO-002 - ЗАПРЕЩЕНО.** Workspace package не может импортировать код из `apps/*`.
**SLM-MONO-003 - ЗАПРЕЩЕНО.** Одно приложение не может deep-import исходники другого приложения вместо общего package contract.
## Package boundary
**SLM-MONO-004 - ОБЯЗАН.** Package должен иметь самостоятельного owner, public exports и подтверждённую reuse/ownership semantics.
**SLM-MONO-005 - ЗАПРЕЩЕНО.** Нельзя создавать package только для обхода layer direction или скрытия cross-domain import.
**SLM-MONO-006 - ОБЯЗАН.** Consumers импортируют package через объявленный package export, а не через filesystem path к internal source.
## Типичные packages
Допустимыми кандидатами являются:
- product-agnostic UI kit;
- technical infra client;
- deterministic shared foundation;
- schema/codegen/tooling package;
- configuration package без application graph.
## Domains в packages
Эта draft-версия определяет Domain как module внутри `apps/{app}/src/domains` и пока не определяет packaged domain как conforming SLM Domain.
**SLM-MONO-007 - ОБЯЗАН.** До принятия отдельной package-модели Domain должен оставаться внутри владеющего приложения.
**SLM-MONO-008 - ЗАПРЕЩЕНО.** Package не может называться Domain для целей этой версии Specification, если он не соответствует определённому application path и ownership.
## Dependency direction
**SLM-MONO-009 - ОБЯЗАН.** Package dependency graph должен оставаться ацикличным и соответствовать заявленной ответственности packages.
**SLM-MONO-010 - ЗАПРЕЩЕНО.** Shared package не может импортировать domain, composition или app-specific infra package.

View File

@@ -0,0 +1,81 @@
---
title: Public API и импорты
status: draft
normative: true
---
# Public API и Импорты
Public API ограничивает знание consumers о внутренней структуре module и отделяет runtime profiles.
## Общие правила
**SLM-API-001 - ОБЯЗАН.** Межмодульный import должен использовать public entrypoint импортируемого module.
**SLM-API-002 - ЗАПРЕЩЕНО.** Deep imports во внутренние segments, zones и files другого module запрещены.
**SLM-API-003 - ОБЯЗАН.** Каждый runtime export должен иметь реального consumer за пределами владеющего entrypoint и стабильную ответственность. Sibling zone собственного domain и public-boundary test считаются consumers zone entrypoint.
**SLM-API-004 - ЗАПРЕЩЕНО.** Public API не может экспортировать raw Context, mutable store, persistence key, concrete adapter, SDK client или internal service.
**SLM-API-005 - ОБЯЗАН.** Alias или package subpath должен физически разрешаться TypeScript, tests и production build.
## Layer matrix
| Importer | Runtime imports |
|---|---|
| `app` | Public composition entries, shared static/global resources |
| `compositions` | Compositions, domain runtime surfaces, infra, ui, shared |
| Domain `business` | Own files, shared, pure libraries |
| Domain `react` | Own business types, ui, shared, framework libraries |
| Domain `adapters` | Own business types/ports, infra, SDK/platform runtime |
| Domain `client/server` | Own factory, own adapters, own runtime surface |
| `infra` | Infra, shared |
| `ui` | UI, shared |
| `shared` | External pure libraries only |
Cross-domain rules дополнительно ограничены [cross-domain boundary](./layers/domains/cross-domain-boundary.md).
## Type-only imports
**SLM-API-006 - МОЖЕТ.** `import type` может использоваться для разрешённого contract dependency без создания runtime edge.
**SLM-API-007 - ЗАПРЕЩЕНО.** Type-only import не разрешает перенос ownership, импорт concrete runtime type или обход layer boundary.
**SLM-API-008 - СЛЕДУЕТ.** Cross-domain capability следует описывать consumer-owned structural port вместо зависимости от полного foreign API type.
## Business entrypoint
```text
domains/{domain}/business/index.ts
```
Точный contract business entrypoint определён правилами [SLM-BUS-006 - SLM-BUS-008](./layers/domains/business.md#public-api).
## Runtime-specific domain entrypoints
Domain может иметь отдельные public surfaces:
```text
domains/{domain}/react
domains/{domain}/client
domains/{domain}/server
```
Разделение client/server exports и markers определено правилами [SLM-ASM-011 - SLM-ASM-014](./layers/domains/client-and-server.md#public-entrypoints).
Точная форма re-export между `react` и `client` в этом draft не предписана. Независимо от формы должны соблюдаться environment isolation и отсутствие обхода DomainRuntime.
## Groups и private zones
**SLM-API-013 - ЗАПРЕЩЕНО.** Group не имеет public entrypoint.
**SLM-API-014 - ЗАПРЕЩЕНО.** Domain `adapters` не экспортируется app, compositions или другим domains.
**SLM-API-015 - ОБЯЗАН.** Composition public API экспортирует только entry components, точные access hooks/types и contracts, необходимые внешним composition consumers.
## Cycles
**SLM-API-016 - ЗАПРЕЩЕНО.** Runtime import cycle между modules запрещён независимо от того, способен ли bundler его выполнить.
**SLM-API-017 - ЗАПРЕЩЕНО.** Barrel не должен создавать скрытый cycle между ready composition и access API её children.

View File

@@ -0,0 +1,93 @@
---
title: Runtime и lifecycle
status: draft
normative: true
---
# Runtime и Lifecycle
Lifecycle является архитектурной частью любого mutable runtime, subscription и external resource.
## Три стадии
```text
definition
→ module объявляет creators
creation
→ creator создаёт runtime instance без внешних effects
activation
→ graph owner запускает resources и получает cleanup
```
**SLM-LIFE-001 - ЗАПРЕЩЕНО.** Module import не должен выполнять product I/O, открывать connection или регистрировать global listener.
**SLM-LIFE-002 - ОБЯЗАН.** Factory и runtime creator должны быть side-effect free относительно external resources.
**SLM-LIFE-003 - ОБЯЗАН.** Subscription, socket, timer и listener запускаются явной operation владельца scope.
**SLM-LIFE-004 - ОБЯЗАН.** Каждый запущенный resource должен иметь cleanup или dispose contract.
## Scope
| Scope | Примеры владельца |
|---|---|
| Application | Root composition/provider |
| Route branch | Route layout composition |
| Page | Page composition/provider |
| Component flow | Nested composition module |
| Request | Server composition/request builder |
| Test | Test setup/wrapper |
**SLM-LIFE-005 - ОБЯЗАН.** Graph owner должен определить количество instances и duration каждого runtime.
**SLM-LIFE-006 - ЗАПРЕЩЕНО.** Module-level singleton не может использоваться как случайная замена application scope.
**SLM-LIFE-007 - МОЖЕТ.** Application singleton допустим только при явном application ownership и отсутствии request-, identity- и user-specific data.
## Graph activation
**SLM-LIFE-008 - ОБЯЗАН.** Cross-domain graph запускается в dependency order и освобождается в обратном порядке.
**SLM-LIFE-009 - ОБЯЗАН.** Повторный mount/unmount, включая development Strict Mode, не должен оставлять duplicate subscription или abandoned resource.
**SLM-LIFE-010 - СЛЕДУЕТ.** `start` и cleanup следует проектировать idempotent либо явно защищать от повторного вызова.
**SLM-LIFE-018 - ОБЯЗАН.** Если activation графа завершилась ошибкой, graph owner должен освободить уже успешно запущенную часть графа в обратном порядке.
**SLM-LIFE-019 - ОБЯЗАН.** Ошибка cleanup должна быть наблюдаемой и не должна препятствовать попытке освободить остальные resources графа.
## Events и sockets
Socket является technical transport, а его product events входят в domain через business-owned event port.
```text
socket transport
→ domain adapter
→ business event normalization
→ state transition или invalidation intent
→ framework projection
```
**SLM-LIFE-011 - ЗАПРЕЩЕНО.** Framework component не может подписываться на product socket напрямую.
**SLM-LIFE-012 - ОБЯЗАН.** Invalid event и connection failure должны преобразовываться в domain state/outcome либо technical telemetry согласно их semantics; callback error нельзя терять через unobserved throw.
**SLM-LIFE-013 - МОЖЕТ.** Один physical transport может обслуживать adapters нескольких domains, если transport остаётся domain-agnostic, а adapters получают суженные channels.
## Revalidation events
Event может содержать domain update или только сообщать об устаревании данных.
**SLM-LIFE-014 - ОБЯЗАН.** Invalidation intent должен выражаться domain language и не требовать import конкретной query library в business.
Framework surface может преобразовать domain invalidation event в private cache invalidation.
## Server runtime
**SLM-LIFE-015 - ОБЯЗАН.** User-specific server runtime создаётся в request scope.
**SLM-LIFE-016 - ЗАПРЕЩЕНО.** Process singleton не может захватывать request headers, cookies, credentials, AbortSignal или user-specific cache.
**SLM-LIFE-017 - ОБЯЗАН.** Request cancellation должна передаваться external operations через подходящий port/adapter, если runtime поддерживает cancellation.

View File

@@ -0,0 +1,75 @@
---
title: Сегменты
status: draft
normative: true
---
# Сегменты
Segment группирует внутренние файлы module по устойчивой роли. Segment не является самостоятельным layer, module или domain.
## Базовые segments
| Segment | Роль |
|---|---|
| `ui/` | Presentation components текущего module |
| `parts/` | Nested modules текущего module |
| `hooks/` | Framework hooks текущей ответственности |
| `providers/` | Provider implementations текущего module |
| `stores/` | Concrete state runtime текущего owner |
| `services/` | Scenario operations и service objects |
| `mappers/` | Transformation на границе ответственности |
| `types/` | Types текущего module |
| `styles/` | Styles текущего module |
| `lib/` | Небольшие internal utilities |
| `config/` | Constants и configuration текущего module |
| `tests/` | Tests публичной границы или составного runtime |
## Правила
**SLM-SEG-001 - МОЖЕТ.** Module может использовать любые необходимые segments и не обязан создавать остальные.
**SLM-SEG-002 - ЗАПРЕЩЕНО.** Нельзя создавать полный симметричный набор segments как scaffold без реального содержимого.
**SLM-SEG-003 - ОБЯЗАН.** Файл должен размещаться в segment согласно своей фактической роли, а не только расширению или имени.
**SLM-SEG-004 - ЗАПРЕЩЕНО.** Segment не имеет внешнего public API независимо от module owner.
**SLM-SEG-005 - ЗАПРЕЩЕНО.** Нельзя импортировать segment другого module через deep path.
## UI и Parts
`ui/` содержит presentation components без самостоятельного architectural ownership.
`parts/` содержит nested modules с собственной внутренней структурой и локальным public boundary.
**SLM-SEG-006 - ОБЯЗАН.** Сущность с самостоятельной ответственностью, внешними архитектурными dependencies или nested modules должна размещаться в `parts`, а не маскироваться как плоский component. Локальные presentation hooks/state сами по себе не требуют `parts`.
## Hooks
**SLM-SEG-007 - ОБЯЗАН.** Hook принадлежит тому module, чью ответственность и runtime он выражает.
Примеры:
- domain hook - `domains/{domain}/react/hooks`;
- page-local hook - владеющая page composition;
- reusable technical hook - соответствующий infra module;
- product-agnostic UI hook - владеющий UI module.
## Domain zones и segments
**SLM-SEG-008 - ЗАПРЕЩЕНО.** Domain zones `business`, `react`, `adapters`, `client`, `server` нельзя трактовать как взаимозаменяемые generic segments.
Внутри zone могут существовать обычные segments:
```text
domain/
├── business/
│ ├── services/
│ ├── types/
│ └── mappers/
└── react/
├── hooks/
├── providers/
└── ui/
```

View File

@@ -0,0 +1,70 @@
---
title: State и data
status: draft
normative: true
---
# State и Data
Данные и состояние должны иметь одного понятного владельца semantics, даже если runtime использует несколько caches и projections.
## Ownership matrix
| Вид | Владелец |
|---|---|
| Domain model и transitions | Domain business |
| Product source integration | Domain adapter |
| Framework projection доменных данных | Domain framework surface |
| Page-local presentation state | Composition |
| Component-local interaction | Владеющий component/module |
| Technical connection/cache state | Infra или runtime-specific owner |
| Request context | Server/framework scope |
| Universal UI state | Владеющий UI module |
## Domain gateway
**SLM-DATA-001 - ОБЯЗАН.** Consumer получает product data только через public domain runtime surface.
**SLM-DATA-002 - ЗАПРЕЩЕНО.** Composition, UI или app не могут маппить transport DTO в параллельную domain model.
**SLM-DATA-003 - ОБЯЗАН.** Domain business владеет normalization, validation и semantics отсутствия данных.
## Domain state
**SLM-DATA-004 - ОБЯЗАН.** Domain state model и допустимые transitions определяются business независимо от concrete state manager.
**SLM-DATA-005 - ЗАПРЕЩЕНО.** Raw store API не может становиться public domain contract.
**SLM-DATA-006 - ОБЯЗАН.** Mutable domain instance должен быть привязан к явному lifecycle scope.
## Query cache
Framework или technical query cache может хранить projection результата DomainRuntime query.
**SLM-DATA-007 - ОБЯЗАН.** Fetcher продуктового query должен вызывать DomainRuntime, а не adapter или SDK напрямую.
**SLM-DATA-008 - ЗАПРЕЩЕНО.** Query cache не может объявлять собственную domain model, error taxonomy или fallback policy.
**SLM-DATA-009 - ОБЯЗАН.** User/session-scoped cache keys и invalidation должны изолировать данные разных identities и scopes без использования secret как публичного key contract.
Эта draft-версия не предписывает единственное физическое место QueryClient/SWR cache. Конкретная модель должна сохранять правила gateway, lifecycle и identity isolation.
**SLM-DATA-015 - ОБЯЗАН.** Cache instance должен иметь явного creator и scope owner в composition или runtime setup.
**SLM-DATA-016 - ОБЯЗАН.** Shared framework cache должен передаваться domain surfaces через framework-supported runtime boundary, а не через import app-specific infra singleton.
**SLM-DATA-017 - ОБЯЗАН.** Graph owner должен очищать или изолировать private cache при смене identity и завершении соответствующего scope.
## Presentation state
**SLM-DATA-010 - МОЖЕТ.** Composition или component может использовать concrete state manager для локального presentation state.
**SLM-DATA-011 - ЗАПРЕЩЕНО.** Presentation store не должен копировать DomainRuntime state как второй source of truth.
## Serializable boundaries
**SLM-DATA-012 - ОБЯЗАН.** Через server/client boundary передаются только serializable business-owned data без functions, stores, clients, Context и resources.
**SLM-DATA-013 - ЗАПРЕЩЕНО.** Secrets, access tokens и request credentials не должны включаться в client bootstrap snapshot.
**SLM-DATA-014 - ОБЯЗАН.** Server и client initial snapshots должны быть согласованы, если framework выполняет hydration одного UI state.

View File

@@ -0,0 +1,112 @@
---
title: Терминология
status: draft
normative: true
---
# Терминология
## Слой
**Layer** - верхнеуровневая зона `src`, определяющая вид ответственности и допустимые направления зависимостей.
SLM использует слои `app`, `compositions`, `domains`, `infra`, `ui` и `shared`.
## Модуль
**Module** - минимальный самостоятельный владелец ответственности с public boundary. Модуль может содержать код разных технических типов, если весь этот код принадлежит одной ответственности.
## Группа
**Group** - навигационная папка, классифицирующая модули или другие группы. Группа не является модулем, не имеет public API и не владеет runtime.
## Домен
**Domain** - конечный продуктовый модуль в слое `domains`, владеющий одной предметной ответственностью и всеми её runtime surfaces.
Допустимые пути:
```text
domains/{domain}
domains/{group...}/{domain}
```
## Группа доменов
**Domain group** - группа внутри `domains`, используемая только для навигации. Например, `knv` в пути `domains/knv/auth` является группой, если не имеет собственного public API, состояния и assembly.
## Зона домена
**Domain zone** - внутренняя архитектурная часть domain с отдельным направлением зависимостей. Базовые зоны: `business`, `react`, `adapters`, `client`, `server`.
Зона не является самостоятельным domain.
## Business
**Business** - framework-neutral зона domain, владеющая моделью, правилами, ports, сценариями, состоянием, нормализацией и domain errors. Business создаёт public logic runtime через factory.
## Factory
**Factory** - side-effect-free constructor, принимающий явные dependencies и возвращающий public business runtime API.
## DomainRuntime
**DomainRuntime** - созданный factory экземпляр доменного поведения. Он предоставляет commands, queries, snapshots, subscriptions и lifecycle operations, необходимые конкретному domain.
Factory создаёт DomainRuntime. DomainRuntime является публичным шлюзом к данным и поведению domain.
## Port
**Port** - business-owned contract внешней capability, необходимой domain. Port описывается языком domain и не раскрывает concrete SDK, transport или framework runtime.
## Adapter
**Adapter** - concrete реализация port поверх infra, SDK, storage, platform API, framework runtime или другого внешнего механизма.
Готовая capability одного DomainRuntime, структурно удовлетворяющая port другого domain, является cross-domain runtime dependency, а не concrete adapter автоматически. Wrapper adapter требуется только при реальном преобразовании contracts.
## Framework surface
**Framework surface** - API domain для конкретного UI/framework runtime. Для React он может включать runtime access boundary, hooks, Providers и domain UI.
Framework surface не является параллельным business API и не обращается к external source в обход DomainRuntime.
## Assembly
**Assembly** - связывание factory с concrete adapters и runtime-specific input для создания готового runtime одного domain.
## Composition
**Composition** - модуль, связывающий готовые modules и domain runtimes в page, route, layout, screen, widget или другой application flow.
## Graph owner
**Graph owner** - composition, request setup или test setup, которое выбирает набор runtime instances, порядок их создания, lifecycle scope и cleanup.
## Domain runtime Provider
**Domain runtime Provider** - часть framework surface, передающая готовый runtime instance framework consumers одного domain. Она не является владельцем cross-domain graph автоматически.
## Provider composition
**Provider composition** - composition module, создающий или получающий несколько runtimes и монтирующий их framework boundaries в выбранном scope.
## Segment
**Segment** - внутренняя папка модуля, группирующая файлы по роли, например `hooks`, `services`, `types`, `styles` или `lib`.
Domain zones не являются обычными segments.
## Компонент
**Component** - presentation unit внутри владеющего module. Компонент не является самостоятельным архитектурным owner и не выбирает источники данных или runtime dependencies.
## Продуктовые данные
**Product data** - данные, состояние и outcomes, имеющие смысл в предметной области продукта. Transport DTO, raw SDK response и browser storage schema не являются доменной моделью автоматически.
## Runtime dependency
**Runtime dependency** - dependency, необходимая выполняемому коду: API другого объекта, external source, store, query runtime, event source, clock, environment или platform capability.
`import type` не создаёт runtime dependency, но может создавать статическую связанность contracts.

View File

@@ -0,0 +1,82 @@
---
title: Тестирование и соответствие
status: draft
normative: true
---
# Тестирование и Соответствие
Тесты проверяют public boundaries и runtime risks каждого owner, а не только внутренние helpers.
## Business factory tests
**SLM-TEST-001 - ОБЯЗАН.** Каждый public method runtime API, возвращаемого factory, должен иметь factory-level tests.
Factory-level tests должны проверять применимые случаи:
- happy path;
- malformed external result;
- rejected dependency;
- синхронное исключение dependency;
- domain outcome/error semantics;
- side-effect order;
- state transition;
- отсутствие constructor-time I/O;
- public API shape.
**SLM-TEST-002 - ОБЯЗАН.** Factory-level test должен создавать runtime через public `business` entrypoint, а не deep-import factory internals.
## Adapter tests
**SLM-TEST-003 - ОБЯЗАН.** Adapter с mapping, transport payload, error channel или lifecycle должен иметь contract tests на применимые responsibilities.
**SLM-TEST-004 - ЗАПРЕЩЕНО.** Adapter test не должен дублировать business scenario tests или утверждать domain fallback/error semantics.
## Assembly tests
**SLM-TEST-005 - ОБЯЗАН.** Client/server assembly tests должны проверять корректную передачу ports, runtime profile isolation и отсутствие I/O при creation.
**SLM-TEST-006 - ОБЯЗАН.** Server assembly с request data должен иметь isolation test для параллельных scopes.
## Framework tests
**SLM-TEST-007 - ОБЯЗАН.** Framework surface tests должны проверять runtime access boundary, предсказуемую ошибку при отсутствии runtime boundary, mapping public outcomes и lifecycle integration.
**SLM-TEST-008 - ОБЯЗАН.** Client/server import boundary должна проверяться инструментом, понимающим реальный framework module graph; DOM unit test не заменяет production build probe.
## Composition tests
**SLM-TEST-009 - ОБЯЗАН.** Tests cross-domain composition должны проверять topology, точный graph contract, переданные capabilities и lifecycle cleanup.
**SLM-TEST-010 - ОБЯЗАН.** Scope с неполным набором domains не должен типизироваться как полный application graph.
## Architecture conformance
Repository checks должны проверять применимые ограничения:
- направление imports;
- deep imports;
- public entrypoints;
- runtime cycles;
- client/server markers;
- forbidden cross-domain imports;
- unique rule IDs документации;
- generated artifacts, если они используются.
**SLM-TEST-011 - ЗАПРЕЩЕНО.** Документированное правило не считается механически enforced, если repository tooling его фактически не проверяет.
## Единица соответствия
**SLM-TEST-014 - ОБЯЗАН.** Application соответствует Specification, если все его modules и связи выполняют применимые обязательные правила.
**SLM-TEST-015 - ОБЯЗАН.** Изменение соответствует Specification, если новые и изменённые modules не создают новых нарушений и проходят применимые checks.
**SLM-TEST-016 - ОБЯЗАН.** Отступление от правила `СЛЕДУЕТ` должно быть зафиксировано в архитектурном review или принятом decision с указанием причины и scope.
**SLM-TEST-017 - ОБЯЗАН.** Manual conformance и mechanical enforcement должны различаться явно; отсутствие автоматической проверки не отменяет нормативное правило.
## Completion gate
**SLM-TEST-012 - ОБЯЗАН.** Изменение считается завершённым только после выполнения ближайших tests, typecheck, lint, build и architecture checks, существующих в repository.
**SLM-TEST-013 - ОБЯЗАН.** Невыполненная проверка и остаточный риск должны быть явно указаны в результате работы.