---
doc_id: ARC-CK8S-SERVICE-BOUNDARIES-0001
type: design-doc
title: kodex — границы сервисов
status: active
owner_role: SA
created_at: 2026-04-26
updated_at: 2026-05-29
related_issues: [599, 600, 601, 602, 655, 711, 725, 747, 753, 778, 781, 786, 852, 322, 782, 834, 865, 867, 883, 976]
related_prs: []
related_adrs: []
approvals:
  required: ["Owner"]
  status: approved
  request_id: "owner-2026-04-26-platform-architecture-frame"
  approved_by: "ai-da-stas"
  approved_at: 2026-04-26
---

# Границы сервисов

## Кратко

Новая реализация пишется как набор сервисов-владельцев, а не как новый большой `control-plane`. Каждый сервис-владелец владеет своим состоянием, контрактами, миграциями, событиями и бизнес-правилами. Пограничный слой, worker и agent-runner не получают доменного владения.

## Цели

- Зафиксировать, какой сервис владеет каждым классом решений.
- Запретить прямое чтение и изменение чужих БД.
- Не допустить повторного появления доменного монолита под видом шлюза, worker или operations-сервиса.
- Сохранить модель, где провайдер остаётся источником рабочих артефактов, без внутреннего заменителя `Issue` и `PR/MR`.
- Разделить агентный запуск, задание среды исполнения, слот и артефакт провайдера.

## Не-цели

- Не фиксировать физическую структуру каталогов реализации.
- Не описывать полные SQL-схемы каждого сервиса.
- Не проектировать GitLab или платёжные провайдеры детально.
- Не переносить старый код из `deprecated/**` в новые сервисы.

## Базовые правила границ

### Один владелец для канонической истины

Если два сервиса одновременно меняют одно и то же каноническое состояние, граница неверна. Проекция чтения может дублировать данные для пользовательского интерфейса, но не может принимать бизнес-решения вместо сервиса-владельца.

### Сервис владеет правилами и состоянием

Нельзя оставить данные в одном сервисе, а бизнес-переходы в другом. Если переход меняет каноническое состояние, его выполняет сервис-владелец.

### Пограничный слой остаётся тонким

Gateway-сервисы, `web-console`, `platform-mcp-server` и `codex-hook-ingress` не должны становиться местом, где живут правила проекта, доступа, жизненного цикла агентного запуска, жизненного цикла задания или состояния провайдера.

### Исполнители не владеют доменом

`worker` и `agent-runner` исполняют задачи по поручению сервисов-владельцев. Они не вводят собственные статусы, которые становятся важнее доменного состояния.

### Межсервисные связи не являются SQL-связями

Ссылки на чужие агрегаты хранятся как внешние идентификаторы. Целостность между сервисами-владельцами поддерживается через API, доменные события, проекции чтения и сверку, а не через `FOREIGN KEY` между БД.

`platform-event-log` — отдельное инфраструктурное исключение для доставки доменных событий. Он не владеет бизнес-сущностями, но владеет своей БД, миграциями общего журнала и checkpoint потребителей. Сервисы-владельцы могут подключаться к нему как публикаторы или потребители через общий клиент и `libs/go/eventconsumer`, но не создают таблицы `platform_event_log` в своих БД. Consumer runtime доставляет событие до доменного use-case сервиса-владельца; бизнес-логика и persistent diagnostic/status остаются в этом сервисе.

## Сервисы-владельцы и запреты

| Сервис | Владеет | Не владеет |
|---|---|---|
| `access-manager` | Пользователи, организации, группы, членство, allowlist, решения доступа, внешние аккаунты как субъекты политики, ссылки на секреты, административный аудит. | Проекты, зеркало провайдера, webhook, лимиты провайдера, получение значений секретов, жизненный цикл агентных запусков и заданий, проекции интерфейса. |
| `project-catalog` | Проекты, репозитории, `services.yaml`, checked project policy projection, project-side onboarding signal reconciliation journal, источники документации, правила веток, релизная политика, релизные линии, ссылки на risk profile, политика размещения. | Webhook провайдера, lightweight provider scan snapshot как источник политики, provider-native запись, роли и промпты, жизненный цикл слота, risk/gate decisions, уведомления. |
| `provider-hub` | Webhook, зеркало провайдера, синхронизация, лимиты, операции провайдера, облегчённый provider-side snapshot adoption scan без содержимого файлов, безопасные merge signals, gRPC-поверхность чтения этих provider-owned фактов, журнал операций, проверка ссылки на governance gate в командах записи и операционное состояние авторизации по внешнему аккаунту. | Канонический реестр внешних аккаунтов, область применения аккаунта, правила доступа, процессы, выбор роли, deep workspace scan/report, checked artifact registry/payload, project/adoption decisions, risk/gate decisions, задания сборки и выкладки в слотах, релизная политика. |
| `package-hub` | Каталог пакетов, установленные пакеты, доступные пакеты, источники пакетов, верификация, схемы секретов, ценовые метаданные. | Жизненный цикл нагрузок плагинов, Git-истина провайдера, членство пользователей. |
| `agent-manager` | Процессы, этапы, роли, промпты, агентные запуски и сессии, безопасная persistent activity timeline, правила автоматизации, приёмка, состояния ожидания flow, Human gate wait/result refs и normalized outcome для orchestration. | Детали API провайдера, исполнение слотов и заданий, governance/risk/release decision body, delivery уведомлений, interaction callback payload, raw tool payload/logs, prompt/transcript/PII. |
| `fleet-manager` | Серверы, Kubernetes-кластеры, связность, здоровье, политика размещения. | Жизненный цикл агентного запуска, состояние провайдера, статус задания как истина среды исполнения. |
| `runtime-manager` | Жизненный цикл слота, подготовка workspace, платформенные задания, сборка, выкладка, зеркалирование, prewarm/reuse, очистка, статус среды исполнения, срок хранения. | Продуктовый смысл задач, агентные запуски, risk/gate/release decisions, артефакты провайдера, серверы и Kubernetes-кластеры как реестр fleet. |
| `governance-manager` | Риск-профили, правила риска, оценка риска, review signals, gate policy, gate requests, gate decisions, release decision package, release decisions, blocking signals, release safety-loop state. | Проектная политика, flow/run/acceptance как процесс, provider-native артефакты, runtime job execution, доставка уведомлений, callback transport, UI/gateway. |
| `billing-hub` | Биллинговые аккаунты, записи затрат, распределение, основа счёта, экономика пакетов. | Истина среды исполнения, истина провайдера, граф доступа. |
| `interaction-hub` | Диалоги, доставка запросов согласования, уведомления, подписки, внешние обратные вызовы, попытки доставки, авторитетное чтение owner inbox по собственным interaction-сущностям. | Бизнес-логика процессов, risk/gate/release decisions, канонические статусы агентных запусков и заданий, состояние проекта, cross-domain operations inbox. |
| `operations-hub` | Модели чтения, ленты событий, операторские очереди, агрегированные статусы и cross-domain inbox. | Первичная истина пользователя, проекта, провайдера, агентного запуска, задания или interaction lifecycle. |

Исполнение Kubernetes-заданий не меняет эту границу: `runtime-manager` создаёт ограниченные workload-объекты только после placement-решения `fleet-manager`, читает cluster access ref через API владельца fleet и разрешает kubeconfig только в памяти процесса. Реестр кластеров, health и правила placement остаются у `fleet-manager`; runtime сохраняет только `fleet_scope_id`, `cluster_id`, namespace и безопасные runtime artifact refs.

## Общие библиотеки на границе сервисов

Общая библиотека может закрывать технический контракт между сервисами, но не должна становиться владельцем доменной политики. `libs/go/secretresolver` — такой технический контракт: `access-manager` остаётся владельцем внешних аккаунтов и ссылок на секреты, а вызывающий сервис после положительного решения доступа получает значение по ссылке только в памяти процесса. Backend хранилища секретов (`kubernetes_mounted_secret`, `env`, `vault` или будущий тип) является деталью resolver-клиента и не переносится в доменную модель `provider-hub` или `package-hub`.

## Пограничные компоненты

### `web-console`

Отвечает за пользовательский интерфейс, маршрутизацию, состояние на стороне клиента и доступность операторских экранов. Не рассчитывает доменные переходы и не соединяется напрямую с БД сервисов-владельцев.

Первый активный `web-console` живёт в `services/staff/web-console`, строится на Vue/Vite/TypeScript/Vuetify и работает только через `staff-gateway`. В MVP он покрывает каркас приложения, командный центр, owner inbox и просмотр одного `AgentRun` с runtime summary и activity timeline. Production-сборка не утверждает actor identity сама: `X-Kodex-Actor-*` добавляет trusted edge или backend-session слой, а ручной actor ввод разрешён только для изолированного local-dev режима. Если для экранного блока нет OpenAPI-ручки в `staff-gateway`, frontend показывает отключённое или пустое состояние, а не создаёт скрытую бизнес-логику или демо-данные.

### Gateway-сервисы

Отвечают за входящий HTTP-трафик, аутентификацию и авторизацию на границе, базовое ограничение частоты запросов и маршрутизацию. Поверхности разделяются по направлениям: внешний пользовательский доступ, доступ сотрудников и администраторов, внешние интеграции и webhook. Gateway-сервисы не хранят бизнесовые статусы и не реализуют доменные сценарии.

Порядок реализации gateway-сервисов задаётся потребностью в их входящих контрактах:
- `staff-gateway` появляется рядом с `web-console`, потому что именно пользовательский интерфейс определяет первый набор операторских и административных OpenAPI-эндпоинтов.
- `integration-gateway` появляется после `provider-hub`, когда известны webhook, обратные вызовы и внешние события, которые нужно принимать на границе платформы.
- `user-gateway` можно сдвинуть на границу MVP или после MVP, пока не подтверждены сценарии внешних пользователей.
- Общий `api-gateway` не создаётся: каждый gateway-сервис отвечает за своё направление доступа и не становится местом доменной оркестрации.

`integration-gateway` принимает внешние webhook и callback события. Он проверяет source binding, подпись, content type, размер payload, лимиты, redaction и backpressure, после чего вызывает сервис-владелец по gRPC. Активные MVP-маршруты: provider webhook -> `provider-hub.IngestWebhookEvent` и generic channel callback -> `interaction-hub.RecordChannelCallback`. `integration-gateway` не хранит provider projections, cursors, операции провайдера, состояние доставки внешнего канала или business decisions; эти данные остаются у `provider-hub`, `interaction-hub`, `package-hub` или другого владельца.

Подробный пакет границы: `docs/domains/integration-gateway/**`.

Первый активный контур `staff-gateway` — OpenAPI для входящих решений владельца и безопасного чтения состояния `AgentRun`. Он отдаёт список `owner inbox`, карточку одного решения и принимает безопасный ответ через gRPC-вызовы `interaction-hub`: `ListOwnerInboxItems`, `GetOwnerInboxItem` и `RecordInteractionResponse`. Для runtime summary он вызывает `agent-manager.GetAgentRunRuntimeStatus` и возвращает только safe refs/status/summary/timestamps/version, включая `runtime_job_ref`, `runtime_job_status` и признак ожидания Human gate. Для истории действий Run он вызывает `agent-manager.ListAgentActivities` и отдаёт только safe activity entries: refs/status/timestamps/tool metadata, safe summary, payload digest, bounded error, version и correlation id. Для governance summary gateway вызывает `governance-manager.GetGovernanceSummary` и только маппит готовые pending/completed decisions, risk class, gate/release outcomes и evidence refs в HTTP-ответ. Для self-deploy наблюдения gateway вызывает `agent-manager.ListSelfDeployPlans` и отдаёт latest safe plan summary: provider signal ref/status, project/repository/source refs, affected service keys/path categories, `services.yaml` digest, plan fingerprint, governance refs/status, expected runtime job types и сохранённые runtime build context/build/deploy refs/status. Когда plan ещё не создан, gateway может уточнить pre-plan остановку через существующие read RPC `project-catalog.GetSelfDeploySignal` и `project-catalog.ListRepositories`, но не читает БД и не становится владельцем project/provider/governance lifecycle. HTTP summary содержит owner-facing `chain_status`, `next_step` и runtime stage summary для состояний от отсутствующего project id или repository binding до pending governance gate, approval, build context, build, deploy и blocker. Gateway получает actor context от доверенной пограничной проверки, передаёт actor/request context дальше, проверяет HTTP path/query/body по контракту, маппит ошибки в HTTP-статусы и не хранит собственную модель решений, delivery lifecycle, `Run`, session, runtime job, governance decision, provider write, activity timeline, deploy plan lifecycle или cross-domain inbox. Список использует фильтры и cursor pagination, а порядок карточек остаётся доменной политикой `interaction-hub`, без настраиваемой сортировки в gateway. В ответах остаются только safe refs, статусы, краткие summaries, timestamps и version; raw provider payload, raw webhook body, raw tool input/output, stdout/stderr, секреты, полный prompt/transcript, callback payload, diff, полный YAML, kubeconfig, workspace paths и большие логи не возвращаются.

### `platform-mcp-server`

Отвечает за MCP-поверхность инструментов, проверки политик, аудит MCP-вызовов и маршрутизацию к сервисам-владельцам. Не хранит каноническое состояние агентного запуска, задания, проекта, пакета или артефакта провайдера.

Provider-инструменты в MCP должны оставаться типизированными на внешней поверхности. Для операций записи MCP передаёт в `provider-hub` типизированную команду, выбранный `external_account_id`, безопасный `operation_policy_context` и `approval_gate_ref`, если политика по риску требует gate. MCP и `agent-manager` получают risk/gate/release decision refs из `governance-manager`, а `provider-hub` владеет только исполнением provider-команды, GitHub/GitLab-адаптером записи и журналом операции. Release package, blocking signal и safety-loop state не копируются в MCP и остаются состоянием `governance-manager`.

Контекст руководящих пакетов в workspace не меняет границы сервисов: `agent-manager` выбирает и замораживает refs руководящих пакетов, `package-hub` остаётся владельцем package/install/version/source/manifest истины, `project-catalog` отдаёт проверенную workspace policy, а `runtime-manager` материализует источники `guidance_package` и сгенерированный контекст в workspace. Ни MCP, ни `agent-manager` не делают checkout руководящих пакетов. Runtime-контур строит локальные пути только через `safe_local_name` и перед checkout читает тип источника, commit и идентичность источника из `package-hub` по `package_version_ref`, а не выводит способ получения из произвольной строки `source_ref`.

Подробный пакет границы: `docs/domains/platform-mcp-server/**`.

### `codex-hook-ingress`

Отвечает за приём нормализованных Codex hook events от hook emitter или локального sidecar: `SessionStart`, `UserPromptSubmit`, `PreToolUse`, `PermissionRequest`, `PostToolUse` и `Stop`. Это не MCP-сервер и не владелец бизнес-состояния. Codex запускает hooks как command-обработчики, поэтому hook emitter получает JSON от Codex, очищает данные и отправляет безопасный envelope во входной контур платформы.

`codex-hook-ingress` принимает только нормализованный и очищенный envelope, проверяет actor/source/run/session/slot binding и маршрутизирует событие владельцу. Hook emitter/local sidecar выполняется внутри slot/runtime boundary, применяет sanitizer до buffer/send и использует логическую операцию `SubmitHookEvent`; физический gRPC/HTTP transport выбирается отдельным срезом. `integration-gateway` не принимает slot-local hook events, потому что его зона — внешние webhook/callback. Сырые tool payload, значения секретов, большие stdout/stderr, kubeconfig, provider payload и полные session dumps не хранятся и не передаются дальше. Короткая realtime/ops feed ingress не является долгой историей действий: canonical persistent timeline по session/run хранит `agent-manager`. Контрольные точки сжатия контекста не входят в текущий платформенный MVP-набор Codex hooks и проектируются отдельно как внутренние события `agent-manager`/`runtime-manager`.

Подробный пакет границы: `docs/domains/codex-hook-ingress/**`.

## Исполнительные компоненты

### `worker`

Исполняет фоновые задачи, повторы, доставку outbox, обработку inbox и сверку. Работает через контракты сервисов-владельцев и не читает чужие БД.

### `agent-runner`

Исполняет ролевого агента в слоте. Может работать с кодом, проектной документацией, нативными артефактами провайдера и MCP-инструментами. Не владеет процессом, жизненным циклом агентного запуска, приёмкой или жизненным циклом задания.

## Типы взаимодействия

| Тип | Когда использовать | Ограничение |
|---|---|---|
| Синхронная команда | Нужно изменить каноническое состояние сервиса-владельца. | Команда идёт только к сервису-владельцу. |
| Команда с ожиданием версии | Вызывающая сторона меняет агрегат, который могла прочитать раньше. | Команда передаёт ожидаемую версию; при конфликте сервис-владелец отклоняет изменение, а вызывающая сторона перечитывает актуальное состояние. |
| Короткая блокировка агрегата | Нужно атомарно проверить инвариант и изменить одну или несколько строк внутри БД сервиса-владельца. | Блокировка живёт только внутри короткой транзакции сервиса-владельца и не пересекает границу сервиса. |
| Lease или резервация | Нужно занять слот, задание, внешний аккаунт или другой ресурс на время долгой операции. | Это доменное состояние с владельцем, сроком действия и токеном защиты, а не длинная SQL-блокировка. |
| Авторитетное чтение | Нужно проверить актуальную истину сервиса-владельца. | Не заменяется чтением проекции. |
| Проекция чтения | Нужно быстро собрать пользовательский интерфейс, ленту событий, фильтр или поиск. | Не является источником истины. |
| Доменное событие | Нужно уведомить другие домены о факте изменения. | Подписчик обрабатывает событие идемпотентно. |
| Сверка | Нужно восстановить состояние после потери события или рассинхронизации. | Выполняется владельцем состояния или по его поручению. |
| Общий журнал событий | Нужно доставить доменное событие нескольким потребителям без отдельной шины сообщений в MVP. | Используется только отдельная БД `platform-event-log`; доменный сервис не владеет её миграциями. |

## Конкурентные изменения и блокировки

Если два сервиса почти одновременно хотят изменить одну и ту же сущность, они не меняют её напрямую. Оба отправляют команду в сервис-владелец, а сервис-владелец сериализует изменение внутри своей БД.

Правила:
- У агрегата, который может изменяться конкурентно, должна быть версия, ревизия или другой монотонный маркер изменения.
- Команда, основанная на ранее прочитанном состоянии, должна передавать ожидаемую версию агрегата.
- Сервис-владелец выполняет проверку инвариантов и изменение в одной короткой транзакции.
- Если версия устарела, сервис-владелец возвращает конфликт, а вызывающая сторона выполняет авторитетное чтение и принимает новое решение.
- Если инвариант требует пессимистичной защиты, сервис-владелец может использовать блокировку строки или эквивалентный механизм PostgreSQL внутри своей транзакции.
- Нельзя держать SQL-блокировку на время внешнего вызова, работы агента, сборки, выкладки, ожидания владельца или другой долгой операции.
- Долгие занятости оформляются как доменная lease/резервация с владельцем, сроком действия, причиной и токеном защиты от устаревшего исполнителя.
- Повтор команды после сетевой ошибки должен быть безопасен через идентификатор команды или идемпотентный ключ.

Пример: два сервиса пытаются изменить релизную политику проекта. Оба отправляют команду в `project-catalog` с ожидаемой версией политики. Первая команда проходит и повышает версию. Вторая получает конфликт версии, перечитывает актуальную политику и либо повторяет изменение с новой версией, либо прекращает сценарий.

## Инварианты для реализации

- Новый сервис создаётся только с явным контуром владения.
- У сервиса есть свой контур миграций и свой доступ к БД.
- Нельзя добавлять SQL-зависимость на таблицы другого сервиса-владельца.
- Таблицы общего журнала событий создаются только в контуре `platform-event-log`.
- Общие библиотеки не содержат доменной логики конкретного сервиса.
- Если пользовательский интерфейс требует сложный агрегат, сначала проектируется проекция чтения, а не временная сборка в шлюзе.
- Если агенту нужна опасная операция, она идёт через MCP-инструмент с политикой и аудитом.
- Конкурентно изменяемые агрегаты проектируются с версией и идемпотентными командами.

## Апрув

- request_id: `owner-2026-04-26-platform-architecture-frame`
- Решение: approved
- Комментарий: границы сервисов входят в сквозной архитектурный каркас платформы.
