# Схема БД ## 1. Назначение документа Этот документ фиксирует структуру хранения конфигураций, версий операций, загруженных артефактов и published runtime-view. Его цель - дать основу для SQL-миграций и для реализации `crank-registry`. В документе предполагается реляционная модель, ориентированная на `PostgreSQL`. Канонической считается схема, совместимая с `PostgreSQL`. ## 2. Общие принципы хранения ### 2.1. Версионирование обязательно Конфигурация operation не должна храниться только в одной "живой" записи. Каждое существенное изменение должно приводить к появлению новой версии конфигурации. Для MVP в registry version snapshot хранит protocol-specific конфигурацию, схемы, mapping и execution settings. Поля identity и listing view (`name`, `display_name`, `protocol`) считаются стабильными и хранятся в `operations`. ### 2.2. Published и draft разделяются логически - `draft` может меняться; - `published` должна ссылаться на конкретную зафиксированную версию; - runtime читает только опубликованные версии. ### 2.3. Артефакты и конфигурация не смешиваются `.proto`, descriptor set, sample JSON и YAML import payload не должны храниться в той же структуре, что и runtime-ready configuration. ### 2.4. Секреты не хранятся внутри operation В БД operation должны храниться только ссылки на auth profiles или secret references. Для MVP рекомендуется отдельная таблица `auth_profiles`, где metadata и secret refs отделены от operation versions. ### 2.5. Тестовая изоляция Integration tests для registry должны выполняться на реальной `PostgreSQL`, но без влияния на runtime-данные. Предпочтительный способ: - отдельная test database; - либо отдельная временная schema на время теста; - обязательная очистка после завершения тестов. ## 3. Основные таблицы Минимальный набор таблиц: - `operations` - `operation_versions` - `published_operations` - `operation_samples` - `descriptors` - `auth_profiles` - `yaml_import_jobs` Опционально позже: - `operation_test_runs` - `audit_log` ## 4. Таблица `operations` Хранит стабильную сущность операции, не зависящую от конкретной версии. ### Поля - `id` `text primary key` - `name` `text not null unique` - `display_name` `text not null` - `protocol` `text not null` - `status` `text not null` - `current_draft_version` `integer not null default 1` - `latest_published_version` `integer null` - `created_at` `timestamptz not null` - `updated_at` `timestamptz not null` - `published_at` `timestamptz null` ### Назначение - быстрый список операций; - стабильный идентификатор для UI и MCP; - привязка к актуальному draft и опубликованной версии. ## 5. Таблица `operation_versions` Хранит полную сериализованную конфигурацию конкретной версии operation. ### Поля - `operation_id` `text not null` - `version` `integer not null` - `status` `text not null` - `target_json` `jsonb not null` - `input_schema_json` `jsonb not null` - `output_schema_json` `jsonb not null` - `input_mapping_json` `jsonb not null` - `output_mapping_json` `jsonb not null` - `execution_config_json` `jsonb not null` - `tool_description_json` `jsonb not null` - `samples_json` `jsonb null` - `generated_draft_json` `jsonb null` - `config_export_json` `jsonb null` - `change_note` `text null` - `created_at` `timestamptz not null` - `created_by` `text null` ### Ключи - primary key: `(operation_id, version)` - foreign key: `operation_id -> operations(id)` - рекомендованный composite foreign key для связанных таблиц: `(operation_id, version)` ### Почему так Для MVP выгоднее хранить version snapshot целиком, а не дробить по десятку связанных таблиц. Это: - упрощает versioning; - упрощает откат; - упрощает YAML export; - хорошо сочетается с JSON-oriented доменной моделью. ## 6. Таблица `published_operations` Хранит явную published-привязку, которую читает runtime. ### Поля - `operation_id` `text primary key` - `version` `integer not null` - `published_at` `timestamptz not null` - `published_by` `text null` ### Назначение - быстрый доступ к published runtime-view; - отсутствие двусмысленности, какая именно версия сейчас активна; - простой invalidation для runtime cache. ### Рекомендуемая целостность - `operation_id -> operations(id)` - `(operation_id, version) -> operation_versions(operation_id, version)` ## 7. Таблица `operation_samples` Хранит метаданные и ссылки на sample artifacts. ### Поля - `id` `text primary key` - `operation_id` `text not null` - `version` `integer not null` - `sample_kind` `text not null` - `storage_ref` `text not null` - `content_type` `text not null` - `file_name` `text null` - `created_at` `timestamptz not null` ### Варианты `sample_kind` - `input_json` - `output_json` - `yaml_import_source` ### Назначение - не класть большие sample payload в основные version records; - иметь возможность переиспользовать или пересобирать draft mapping; - отслеживать, из каких sample-данных строился черновик. ### Рекомендуемая целостность - `operation_id -> operations(id)` - `(operation_id, version) -> operation_versions(operation_id, version)` ## 8. Таблица `descriptors` Хранит gRPC schema artifacts. ### Поля - `id` `text primary key` - `operation_id` `text null` - `version` `integer null` - `descriptor_kind` `text not null` - `storage_ref` `text not null` - `source_name` `text null` - `package_index_json` `jsonb null` - `created_at` `timestamptz not null` ### Варианты `descriptor_kind` - `proto_upload` - `descriptor_set` - `reflection_snapshot` ### Назначение - связывать gRPC operation с конкретной схемой; - не хранить binary descriptor внутри основной operation version; - иметь отдельную точку для discovery metadata. ### Рекомендуемая целостность - если descriptor привязан к version, то `(operation_id, version) -> operation_versions(operation_id, version)` ## 9. Таблица `yaml_import_jobs` Для MVP можно импортировать YAML синхронно, но таблицу под журнал импорта лучше предусмотреть сразу. ### Поля - `id` `text primary key` - `source_sample_id` `text null` - `status` `text not null` - `format_version` `text not null` - `mode` `text not null` - `result_operation_id` `text null` - `result_version` `integer null` - `error_text` `text null` - `created_at` `timestamptz not null` - `finished_at` `timestamptz null` ### Назначение - аудит импортов; - разбор ошибок валидации; - поддержка будущего async import pipeline. ## 10. Таблица `auth_profiles` Хранит переиспользуемые профили аутентификации для внешних вызовов. ### Поля - `id` `text primary key` - `name` `text not null unique` - `kind` `text not null` - `config_json` `jsonb not null` - `created_at` `timestamptz not null` - `updated_at` `timestamptz not null` ### Варианты `kind` - `bearer` - `basic` - `api_key_header` - `api_key_query` ### Правило `config_json` должен содержать только `secret_ref`, а не открытые секреты. ## 11. Предлагаемая SQL-форма ```sql create table operations ( id text primary key, name text not null unique, display_name text not null, protocol text not null, status text not null, current_draft_version integer not null default 1, latest_published_version integer null, created_at timestamptz not null, updated_at timestamptz not null, published_at timestamptz null ); create table operation_versions ( operation_id text not null references operations(id), version integer not null, status text not null, target_json jsonb not null, input_schema_json jsonb not null, output_schema_json jsonb not null, input_mapping_json jsonb not null, output_mapping_json jsonb not null, execution_config_json jsonb not null, tool_description_json jsonb not null, samples_json jsonb null, generated_draft_json jsonb null, config_export_json jsonb null, change_note text null, created_at timestamptz not null, created_by text null, primary key (operation_id, version) ); create table published_operations ( operation_id text primary key references operations(id), version integer not null, published_at timestamptz not null, published_by text null, foreign key (operation_id, version) references operation_versions(operation_id, version) ); create table auth_profiles ( id text primary key, name text not null unique, kind text not null, config_json jsonb not null, created_at timestamptz not null, updated_at timestamptz not null ); ``` ## 12. Индексы Минимально нужны: - index on `operations(protocol)` - index on `operations(status)` - index on `operation_versions(operation_id, created_at desc)` - index on `published_operations(version)` - index on `operation_samples(operation_id, version)` - index on `descriptors(operation_id, version)` - index on `auth_profiles(kind)` ## 13. Versioning flow ### Создание операции 1. Создается запись в `operations`. 2. Создается версия `1` в `operation_versions`. 3. `current_draft_version = 1`. ### Изменение draft 1. Читается текущий draft. 2. Создается новая версия `n + 1`. 3. В `operations.current_draft_version` пишется новая версия. 4. Published версия не меняется. ### Публикация 1. Берется текущий draft version. 2. В `published_operations` upsert-ится ссылка на эту версию. 3. В `operations.latest_published_version` пишется та же версия. 4. Runtime cache получает сигнал на reload. ### Импорт YAML 1. YAML валидируется. 2. Определяется create или update сценарий. 3. Создается новая запись в `operation_versions`. 4. При необходимости создается запись в `yaml_import_jobs`. ## 14. Что не должно храниться в БД в таком виде - секреты в открытом виде; - runtime cache; - скомпилированные adapter clients; - невалидированные черновики, не приводимые к доменной модели. ## 15. Практический итог Для MVP рекомендован такой подход: - `operations` - стабильная идентичность; - `operation_versions` - полные version snapshots; - `published_operations` - текущая активная версия; - `operation_samples` и `descriptors` - внешние артефакты; - `auth_profiles` - переиспользуемая внешняя аутентификация; - `yaml_import_jobs` - журнал импортов. Эта схема хорошо ложится на `sqlx`, не требует избыточной нормализации и соответствует JSON-oriented модели домена.