Files
crank/docs/database-schema.md
T
2026-03-28 00:58:56 +03:00

366 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Схема БД
## 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 модели домена.