---
doc_id: REF-CK8S-0011
type: data-model
title: "kodex — модель данных и владение состоянием в третьей волне"
status: active
owner_role: SA
created_at: 2026-04-21
updated_at: 2026-04-23
related_issues: [376, 470, 488, 586]
related_prs: []
approvals:
  required: ["Owner"]
  status: approved
  request_id: "owner-2026-04-21-refactoring-wave3"
  approved_by: "ai-da-stas"
  approved_at: 2026-04-21
---

# Модель данных и владение состоянием в третьей волне

## TL;DR
- PostgreSQL остаётся единой платформенной точкой хранения, но в модели `database-per-service`.
- У каждого сервиса-владельца есть свой канонический набор записей; чтение чужой истины идёт через межсервисные контракты, а не через SQL.
- Платформа хранит только то, чего нет у внешних систем или что нужно для оркестрации, безопасности, аудита и наблюдаемости.
- Платформа не заводит собственную сущность образа, не дублирует полный лог `job`, не превращает provider mirror в новый источник истины.
- Регламентные cleanup и health-check `job` считаются тем же классом платформенных `job`, что и сборка/выкладка, и должны быть видимы оператору.
- Flow, профили ролей и шаблоны промптов канонически хранятся в БД; репозиторий платформы содержит только установочные фикстуры для первичной загрузки и seed-обновлений.
- Проектная документация хранится в репозиториях проекта и описывается источниками в `services.yaml`; PostgreSQL хранит только проверочные и операционные проекции, а не копии документации.
- Метаданные пакетов, граф доступа по арендаторам, реестр серверов и кластеров, billing и правила автоматизации также считаются каноническим состоянием платформы и не должны прятаться в ad-hoc `JSONB` без owner-сервиса.

## 1. Цель этой волны
Эта волна фиксирует не физические SQL-схемы и не полный список таблиц по каждой миграции, а каноническую модель владения состоянием:
- какие данные вообще нужны новой платформе;
- какой сервис является владельцем каждого класса данных;
- что хранится в PostgreSQL, а что остаётся во внешней системе;
- что можно зеркалировать, а что нельзя превращать во внутренний дубликат.

Эта волна должна убрать две крайности:
1. старую модель пишем в БД всё подряд;
2. опасную иллюзию, что почти всё можно читать напрямую по месту из GitHub, реестра образов или Kubernetes без собственного платформенного контура.

## 2. Базовые правила хранения

### 2.1. Один владелец данных
Для каждого класса данных должен существовать ровно один сервис-владелец.

Из этого следуют обязательные правила:
- сервис-владелец владеет и правилами, и каноническим состоянием;
- другие сервисы не читают его БД напрямую;
- межсервисное чтение идёт через синхронный контракт сервиса-владельца;
- проекции чтения и витрины не становятся новой истиной.

### 2.2. PostgreSQL — хранилище платформенного состояния, а не склад всего подряд
В PostgreSQL храним:
- каноническое состояние сервисов-владельцев;
- нормализованные и обогащённые зеркала внешних систем, если они реально нужны платформе;
- историю запусков, `job`, сессий, состояний ожидания, уведомлений и попыток доставки;
- аудит и трассируемость;
- короткие хвосты технических логов для быстрого показа в UI.

В PostgreSQL не храним:
- полные сырые логи сборки, выкладки и runtime-контура;
- содержимое реестра образов как собственную модель-владельца платформы;
- полные дубликаты файлов документации на всякий случай;
- постоянные копии всех ответов внешних API без retention-политики.

### 2.3. Database-per-service
Целевая модель:
- один Postgres-контур или кластер допускается;
- у каждого сервиса-владельца отдельная база;
- у каждого сервиса-владельца отдельный пользователь БД;
- миграции принадлежат только владельцу соответствующей базы.

Это каноническая модель и для будущей реализации, и для планирования extraction waves.

### 2.4. JSONB допускается, но не как бесформенный мусор
`JSONB` нужен там, где структура действительно вариативна:
- нормализованные фрагменты provider payload;
- результаты приёмки;
- детали состояния ожидания;
- контекст `job` и внешних ограничений;
- снимки продолжаемых агентных и slot-сессий;
- структурированные короткие статус-отчёты агента;
- настройки policy и маршрутизации.

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

## 3. Что платформа считает своей истиной, а что нет

### 3.1. Внешние системы остаются владельцами своих сущностей
GitHub/GitLab остаются владельцами:
- `Issue`;
- `PR/MR`;
- комментариев;
- review-состояния;
- labels, relationships, milestone, project fields и других нативных полей.

Реестр образов остаётся владельцем:
- образов;
- тегов;
- manifest и blob-объектов.

Kubernetes остаётся владельцем:
- pod/job/deployment state;
- событий кластера;
- полных контейнерных логов;
- runtime-объектов оркестрации.

Объектное хранилище временных медиаобъектов остаётся владельцем:
- голосовых сообщений;
- изображений и других бинарных вложений, которые пользователь передаёт агенту;
- файлов, которые нужны для speech-to-text, text-to-speech и временного показа в UI.

### 3.2. Что платформа хранит поверх внешней истины
Платформа хранит только необходимые поверхностные слои:
- нормализованные provider mirror-записи;
- платформенные ссылки и обогащения;
- внутренние решения оркестрации;
- slot/job/run/session state;
- текстовые диалоги, метаинформацию голосовых сообщений и ссылки на временные медиаобъекты;
- краткие статус-отчёты агента для UI и внешних каналов;
- результаты acceptance machine;
- уведомления и запросы к человеку;
- проекции чтения для операционного интерфейса.

## 4. Владение состоянием по сервисам

### `access-manager`
Канонически владеет:
- пользователями платформы;
- организациями;
- группами;
- глобальными ролями и членством;
- моделью наследования прав и явных исключений;
- allowlist и состоянием входа;
- административным аудитом доступа.

Не владеет:
- provider mirror;
- run/job/session state;
- проектами и репозиториями.

### `project-catalog`
Канонически владеет:
- проектами;
- репозиториями;
- политика проекта и репозитория;
- путями к `services.yaml`;
- метаинформацией первичной настройки и preflight-проверок;
- привязкой project rules и источников документации из `services.yaml`;
- результатами проверки источников документации и правилами загрузки источников в workspace;
- branch rules;
- release policy;
- привязками guidance packages;
- placement policy проекта и репозитория.

Не владеет:
- webhook-событиями;
- зеркалом `Issue/PR`;
- slot/job/run lifecycle.

### `provider-hub`
Канонически владеет:
- машинными аккаунтами внешних провайдеров, которыми пользуется платформа;
- операционным состоянием этих аккаунтов;
- классом аккаунта и его областью действия по проектам, репозиториям, ролям и операциям;
- нормализованными webhook-событиями;
- зеркалами `Issue/PR/MR/comments/mentions/relationships`;
- обогащением provider-native полей;
- данными о лимитах, блокировках, необходимости переавторизации;
- журналом операций платформы во внешнем провайдере.

Для этой волны под машинными аккаунтами понимаются:
- аккаунты GitHub/GitLab;
- учётные данные для поставщиков моделей и runtime-доступа, если они участвуют в запуске агентов и их лимиты/авторизация должны быть видимы платформе.
- учётные данные внешних MCP-источников, если они требуют отдельной авторизации, имеют собственные лимиты или должны маршрутизироваться по policy.

### `package-hub`
Канонически владеет:
- каталогом пакетов;
- записями каталога пакетов и их версиями;
- метаданными по plugins и guidance packages;
- статусом верификации;
- состоянием установки и импорта пакета;
- схемами секретов пакета;
- ценовыми данными и метаданными каталога.

Не владеет:
- каноническим состоянием репозиториев-источников пакетов у провайдера;
- runtime lifecycle запущенных plugin workloads;
- членством и правами пользователей.

### `agent-manager`
Канонически владеет:
- flow execution state;
- каталогом flow-шаблонов и его версиями;
- профилями встроенных и пользовательских ролей;
- текстами и версиями `work`/`revise` шаблонов;
- версиями flow, digest, областью применения и историей публикации;
- профилями исполнения ролей и правилами запуска дополнительных контрольных ролей;
- agent sessions;
- снимками продолжаемых сессий и их резюмируемым состоянием;
- `run`;
- короткими структурированными статус-отчётами, привязанными к `run` и сессии;
- schedule rules;
- привязками триггеров;
- состоянием исполнения релизной линии;
- состояниями ожидания, связанными с работой агента;
- результатами acceptance machine;
- правилами handover и follow-up.

Не владеет:
- provider mirror;
- slot/job execution details;
- доставкой уведомлений как отдельной доменной сущностью.

### `fleet-manager`
Канонически владеет:
- реестром серверов;
- реестром Kubernetes-кластеров;
- health и connectivity инфраструктурных контуров;
- placement policy;
- привязками организаций, проектов, репозиториев и runtime-контуров к серверным и кластерным контурам.

### `runtime-manager`
Канонически владеет:
- slot lifecycle;
- prewarm/reuse state;
- platform `job`;
- retention и housekeeping policy среды;
- ссылками на технические артефакты runtime-контура;
- краткими итогами операций сборки, выкладки и очистки;
- исполнением runtime на уже выбранном контуре серверов и кластеров.

`runtime-manager` не заводит собственную сущность-владельца образа.
Вместо этого он хранит:
- ссылки на использованные image refs;
- статус операций публикации и выкладки;
- идентификаторы внешних технических объектов, если они нужны для продолжения или диагностики.

### `billing-hub`
Канонически владеет:
- биллинговыми аккаунтами;
- записями затрат;
- распределением затрат;
- основой для выставления счетов;
- ценовыми правилами и правилами распределения выручки.

Не владеет:
- каноническим runtime-состоянием;
- нативными сущностями репозиториев провайдера;
- моделью членства и оргструктурой как первичным контуром доступа.

### `interaction-hub`
Канонически владеет:
- диалоговыми ветками пользователя;
- сообщениями в текстовых и голосовых каналах;
- ссылками на временные медиафайлы и их retention-метаинформацией;
- уведомлениями;
- подписками;
- запросами к человеку;
- callback/resolution state;
- попытками доставки по внешним каналам.

### `operations-hub`
Владеет только:
- проекциями чтения;
- агрегированными витринами;
- индексами и срезами для операторского UI.

`operations-hub` не владеет первичным доменным состоянием даже тогда, когда его проекция чтения выглядит почти как основная таблица.

## 5. Минимальные классы данных по доменам

### 5.1. Доступ и администрирование
Минимально нужны:
- пользователь платформы;
- организация;
- группа;
- глобальная роль;
- участие пользователя в организации, группе и проекте;
- правило наследования и переопределения прав;
- административный журнал аудита;
- состояние входа и список разрешённых адресов.

### 5.2. Проекты и репозитории
Минимально нужны:
- проект;
- репозиторий;
- project/repository policy;
- привязка источников проектной документации и проектных правил из `services.yaml`;
- проверенный состав проектной документации для workspace;
- правила локальных путей для workspace;
- режим доступа к источнику: только чтение или допустимое изменение;
- связь репозитория с сервисом или проектной документацией;
- правила веток;
- релизная политика;
- политика размещения;
- признаки первичного запуска и предварительной проверки.

### 5.3. Provider-native контур
Минимально нужны:
- запись внешнего аккаунта;
- класс аккаунта: платформенный или агентный;
- область доступа аккаунта по проектам, репозиториям, ролям и разрешённым операциям;
- операционный статус аккаунта;
- webhook event journal с retention;
- нормализованные объекты `Issue/PR/MR/comment`;
- отражённые связи и mentions;
- состояние rate-limit/quota;
- записи о попытках внешних операций;
- ссылки на provider-native поля, которые платформа реально использует.

### 5.4. Package platform
Минимально нужны:
- запись каталога пакетов;
- запись подключения к магазину пакетов как к установленному пакету;
- источник доступных пакетов;
- локальный каталог установленных пакетов;
- локальный каталог доступных пакетов, синхронизированный из подключённых магазинов;
- `package kind`;
- репозиторий-источник пакета;
- статус верификации;
- ценовые метаданные;
- запись об установке или импорте пакета;
- схема секретов;
- локализованные метаданные пакета;
- связь пакета с организацией, проектом, репозиторием или платформой.

### 5.5. Агентная оркестрация
Минимально нужны:
- flow template и его версия;
- шаблон этапа flow;
- профиль роли;
- `stage role binding` с типом участия роли в этапе;
- профиль исполнения роли;
- целевой контур исполнения роли и `run`: `code-only`, `full-env`, переиспользование slot или production-контур только для чтения;
- привязка роли к встроенным и внешним MCP-источникам;
- ссылка на `work`-шаблон;
- ссылка на `revise`-шаблон, если он есть;
- идентификатор использованной версии и digest шаблона;
- session;
- резюмируемый снимок session state;
- `run`;
- короткий статус-отчёт `run`;
- состояние ожидания;
- результат классификации намерения;
- результат acceptance-проверки;
- связь между текущим артефактом и следующим ожидаемым шагом;
- ссылка на целевой slot, release-контур или production-контекст, если роль проверяет уже выложенное состояние;
- automation rule;
- привязка триггера;
- состояние релизной линии.

#### Flow, шаблоны промптов и профили ролей в БД
Для этой волны фиксируется жёсткое правило:
- flow, профили ролей и шаблоны промптов канонически хранятся в БД;
- UI-редактирование создаёт новую версию с аудитом, policy-проверкой и возможностью отката;
- агенты в первой версии не редактируют эти объекты напрямую;
- репозиторий платформы содержит установочные фикстуры для первичной загрузки встроенных flow, ролей и шаблонов.

В БД хранится:
- ключ роли и область действия;
- текст flow или шаблона промпта в составе конкретной версии;
- digest или иной идентификатор версии;
- связь с установочной фикстурой, если версия была загружена из seed-файла;
- правила исполнения, policy доступа к MCP и внешним аккаунтам;
- тип участия роли в конкретном этапе только через `stage role binding`, а не как ad-hoc поле профиля роли;
- метаинформацию, нужную для аудита и воспроизводимости запуска.

При запуске агента платформа не читает эти объекты из репозитория. Она берёт утверждённую версию из БД, рендерит prompt и передаёт агенту готовый контекст.

#### Проектная документация и состав workspace
Для этой волны фиксируется отдельное правило:
- проектная документация хранится в репозиториях проекта;
- `services.yaml` описывает источники документации, зависимости, локальные пути и режим доступа;
- runtime получает от `project-catalog` не сырые файлы, а проверенный состав workspace, собранный из `services.yaml`;
- агент в slot работает с локальными копиями источников, но канонической правдой остаются репозитории.

В БД допускается хранить:
- ссылку на источник документации;
- область действия источника;
- связь с сервисом, репозиторием или проектом;
- локальный путь в workspace;
- режим доступа;
- последнюю проверенную версию или digest;
- результат проверки доступности и policy.

В БД не хранится полный граф как отдельная каноническая модель и не хранится копия файлов проектной документации. Источником правды остаются `services.yaml` и репозитории, а БД хранит только проверенное состояние, ошибки загрузки и операционную проекцию, нужную для запуска агента.

### 5.6. Fleet, runtime и platform jobs
Минимально нужны:
- сервер;
- Kubernetes-кластер;
- состояние привязки контура к кластеру;
- slot;
- состояние прогрева и переиспользования;
- `job`;
- тип `job`;
- контекст `job`;
- timestamps и статус;
- причина ошибки;
- краткий хвост лога;
- ссылки на внешние технические объекты.

### 5.7. Billing и cost accounting
Минимально нужны:
- биллинговый аккаунт;
- запись затрат;
- контур распределения;
- основа для выставления счетов;
- связь затрат с использованием провайдера или пакета.

### 5.8. Уведомления и запросы к человеку
Минимально нужны:
- диалоговая ветка;
- сообщение;
- ссылка на временное медиа-вложение;
- уведомление;
- тип события;
- подписка;
- попытка доставки;
- запрос на действие человека;
- ответ или истечение срока ожидания.

### 5.9. Доменные события
Минимально нужны:
- доменное событие с уникальным идентификатором;
- тип агрегата и идентификатор агрегата;
- версия агрегата или монотонный номер изменения;
- тип события и версия схемы события;
- сервис-источник;
- время фиксации события;
- correlation id и causation id;
- payload события без секретов и лишних персональных данных;
- outbox-запись в БД owner-сервиса;
- inbox/checkpoint потребителя для идемпотентной обработки;
- статус доставки и последняя ошибка для диагностики.

Доменное событие фиксирует факт уже совершённого изменения у owner-сервиса. Оно не является удалённой командой другому сервису и не должно навязывать потребителю конкретное действие.

Удаление организации, изменение прав, удаление проекта, отключение внешнего аккаунта, изменение установки пакета и смена состояния `job/run/slot` относятся к событиям, которые должны быть видимы другим доменам детерминированно.

## 6. Политика зеркал и обогащения

### 6.1. Зачем вообще нужен `provider mirror`
`Provider mirror` нужен потому, что платформа не должна:
- собирать каждый экран из живых вызовов GitHub/GitLab;
- принимать решения по устаревшей или частично загруженной картине;
- терять контекст при rate limit или кратковременной недоступности провайдера.

### 6.2. Что разрешено хранить в mirror
Разрешено хранить:
- нормализованный срез provider-native объекта;
- необходимые для платформы provider-native поля;
- технический идентификатор и версию синхронизации;
- обогащение, которое не существует у провайдера;
- ссылку на фрагмент сырого события с retention.

### 6.3. Что запрещено превращать в mirror
Нельзя делать вид, что зеркало заменяет провайдера:
- provider остаётся владельцем review state и mergeability;
- provider остаётся владельцем нативных relationships;
- provider остаётся владельцем комментариев и итогового body;
- при принятии критичного решения нужен канонический запрос к `provider-hub`.

### 6.4. Стратегия досинхронизации и ручного выравнивания
Webhook остаётся основным входящим каналом, но не считается гарантированным транспортом.

Поэтому платформа обязана иметь явную стратегию досинхронизации:
- хранить ровно тот нормализованный срез, который реально нужен owner-сервисам и UI;
- уметь точечно перечитывать конкретный `Issue`, `PR`, комментарии и связи по запросу owner-сервиса;
- уметь запускать фоновую догрузку после пропуска webhook или подозрения на drift;
- учитывать rate limit и другие внешние ограничения при догрузке;
- давать оператору и Owner понятное действие ручного выравнивания конкретной сущности и её связей, если они видят рассинхрон.

Досинхронизация не должна превращаться в постоянный тотальный polling всего провайдера.
Канонический подход:
- webhook как основной сигнал;
- точечное перечитывание по необходимости;
- фоновое восстановление только для проблемных объектов;
- явная операторская кнопка ручного выравнивания.

## 7. Platform jobs и технические журналы

### 7.1. Что должно храниться по `job`
По каждой `job` платформа обязана хранить:
- тип и подтип `job`;
- проект, репозиторий, slot и контекст этапа;
- плановую и фактическую временную линию;
- статус и код причины сбоя;
- признак регламентной или пользовательской `job`;
- краткий хвост лога для быстрого показа;
- ссылку на внешний источник полного лога.

### 7.2. Чего хранить не надо
Не нужно хранить в Postgres:
- полный stdout/stderr сборки и выкладки;
- большие blob-логи контейнеров;
- повторную копию всего, что уже хранит Kubernetes-логирование.

### 7.3. Housekeeping job — полноценная platform `job`
Очистка устаревших slot, cleanup временных данных, retention-задачи и периодические health-check операции считаются обычными platform `job`.

Если такая `job` падает:
- это должно попадать в `runtime-manager`;
- это должно быть видно в `operations-hub`;
- это должно уметь запускать уведомление через `interaction-hub`.

## 8. Retention и ресурсная гигиена

### 8.1. Что подлежит очистке
Платформа должна уметь чистить по policy:
- устаревшие slot;
- временные runtime-данные;
- временные голосовые и графические вложения после истечения retention;
- хвосты технических журналов старше retention;
- устаревшие mirror snapshots и raw event fragments;
- служебные записи, потерявшие операционную ценность.

### 8.2. Что не подлежит собственной очистке платформой как owner
Платформа не должна считать себя владельцем:
- образов в реестре;
- полных логов кластера;
- provider-native сущностей.

Она может управлять только:
- policy и `job`, которые вызывают внешнюю очистку по правилам владельца системы;
- ссылками и результатами этих операций.

## 9. Что intentionally не фиксируется в этой волне
В этой волне не фиксируются:
- точные SQL-таблицы и названия колонок;
- формат protobuf и gRPC-методов;
- точные TTL по всем классам данных;
- окончательная knowledge-модель с `pgvector`.

Но следующие волны реализации и контрактов уже обязаны опираться на эту модель владения состоянием.
