# Модель данных ## 1. Назначение документа Этот документ фиксирует формальную модель данных платформы. Его цель - определить такие структуры, которые можно без существенных изменений перенести в: - Rust domain types, - HTTP DTO, - структуру таблиц БД, - runtime-представление operation, - UI-формы и конфигурационные экраны. Документ не привязан к конкретной СУБД, но задает каноническую JSON-модель сущностей. ## 2. Общие принципы модели ### 2.1. Одна операция - один tool Каждая `Operation` соответствует одному MCP tool. Это особенно важно для: - GraphQL, где одна operation соответствует одному конкретному `query` или `mutation`; - gRPC, где одна operation соответствует одному unary-методу; - REST, где одна operation соответствует одному endpoint-сценарию. ### 2.2. Внутренний транспортный формат - JSON Независимо от внешнего протокола внутри системы данные должны быть представлены в JSON-ориентированном виде. Даже если внешний вызов работает с protobuf, runtime, mapping и UI опираются на нормализованный JSON. ### 2.3. Mapping всегда явный Даже если система умеет строить черновой mapping по примерам данных, итоговая конфигурация mapping должна быть явно сохранена в operation. Нельзя полагаться на неявную "магию" сопоставления во время выполнения. ### 2.4. JSONPath как единый язык адресации Для input и output mapping используется `JSONPath`. Это позволяет единообразно ссылаться на вложенные поля во входе, промежуточном представлении запроса и нормализованном ответе. ### 2.5. YAML как формат обмена конфигурацией Помимо канонической JSON-модели система должна поддерживать импорт и экспорт конфигураций в `YAML`. Это внешний формат обмена, а не отдельная доменная модель. ## 3. Корневая сущность `Operation` `Operation` - основная конфигурационная сущность платформы. ### Поля - `id` - уникальный идентификатор операции. - `name` - стабильное внутреннее имя. - `display_name` - отображаемое имя в UI. - `protocol` - `rest`, `graphql`, `grpc`. - `status` - `draft`, `testing`, `published`, `archived`. - `version` - версия конфигурации операции. - `target` - описание внешней операции. - `input_schema` - схема MCP-входа. - `output_schema` - схема MCP-выхода. - `input_mapping` - правила подготовки внешнего запроса. - `output_mapping` - правила формирования MCP-ответа. - `execution_config` - auth, headers, timeout, retries и protocol-specific execution settings. - `tool_description` - описание tool для MCP и LLM. - `samples` - загруженные образцы JSON и schema artifacts. - `generated_draft` - автоматически построенный черновик схем и mappings. - `config_export` - опциональные метаданные экспортируемой конфигурации. - `created_at` - `updated_at` - `published_at` ### Пример ```json { "id": "op_01hr7w0m6p8x9z4n7s2k3q5t6u", "name": "crm_create_lead", "display_name": "Create Lead", "protocol": "rest", "status": "draft", "version": 3, "target": { "kind": "rest", "base_url": "https://api.example.com", "method": "POST", "path_template": "/v1/leads" }, "input_schema": { "type": "object", "fields": { "name": { "type": "string", "required": true }, "email": { "type": "string", "required": true } } }, "output_schema": { "type": "object", "fields": { "id": { "type": "string", "required": true }, "status": { "type": "string", "required": true } } }, "input_mapping": { "rules": [ { "source": "$.mcp.name", "target": "$.request.body.name" }, { "source": "$.mcp.email", "target": "$.request.body.email" } ] }, "output_mapping": { "rules": [ { "source": "$.response.body.id", "target": "$.output.id" }, { "source": "$.response.body.status", "target": "$.output.status" } ] }, "execution_config": { "timeout_ms": 10000, "auth_profile_ref": "auth_01hr7x8rj2d8nq8v0c4m4t1r9e" }, "tool_description": { "title": "Create CRM lead", "description": "Creates a new lead in CRM by name and email." }, "samples": { "input_json_sample_ref": "file_01hr7xj7z4k1t0qm0cxsw6f1wx", "output_json_sample_ref": "file_01hr7xkvt3m1p6ms3m7r2dr8wz" }, "generated_draft": { "status": "available", "source_types": ["input_json_sample", "output_json_sample"] }, "config_export": { "format_version": "1", "export_mode": "portable" }, "created_at": "2026-03-25T08:00:00Z", "updated_at": "2026-03-25T08:10:00Z", "published_at": null } ``` ## 4. `Target` `Target` описывает конкретный внешний вызов. Это discriminated union по протоколу. ### 4.1. `RestTarget` ```json { "kind": "rest", "base_url": "https://api.example.com", "method": "PATCH", "path_template": "/v1/users/{userId}", "static_headers": { "X-App-Source": "crank" } } ``` Поля: - `kind` - `base_url` - `method` - `path_template` - `static_headers` ### 4.2. `GraphqlTarget` ```json { "kind": "graphql", "endpoint": "https://api.example.com/graphql", "operation_type": "mutation", "operation_name": "CreateLead", "query_template": "mutation CreateLead($input: LeadInput!) { createLead(input: $input) { id status } }", "response_path": "$.response.body.data.createLead" } ``` Поля: - `kind` - `endpoint` - `operation_type` - `operation_name` - `query_template` - `response_path` ### 4.3. `GrpcTarget` ```json { "kind": "grpc", "server_addr": "https://grpc.example.com:443", "package": "crm.v1", "service": "LeadService", "method": "CreateLead", "descriptor_ref": "desc_01hr7yn4d6g1x6vwt7h9n0e7ab", "descriptor_set_b64": "" } ``` Поля: - `kind` - `server_addr` - `package` - `service` - `method` - `descriptor_ref` - `descriptor_set_b64` `descriptor_ref` остается ссылкой на загруженный descriptor artifact в storage и registry. `descriptor_set_b64` - runtime-ready snapshot descriptor set, который используется unary gRPC adapter для динамического вызова метода без генерации Rust-кода. ## 5. `Schema` `Schema` - нормализованное описание входа или выхода. Это не JSON Schema в полном объеме, а внутренняя структурная модель, удобная для UI и runtime. ### Базовая форма ```json { "type": "object", "description": "Lead input", "fields": { "name": { "type": "string", "required": true, "description": "Lead full name" }, "tags": { "type": "array", "required": false, "items": { "type": "string" } } } } ``` ### Поддерживаемые типы - `object` - `array` - `string` - `integer` - `number` - `boolean` - `enum` - `null` - `oneof` ### Модель поля ```json { "type": "string", "required": true, "nullable": false, "description": "User email", "default": null } ``` Тип объекта: ```json { "type": "object", "required": true, "fields": { "email": { "type": "string", "required": true } } } ``` Тип массива: ```json { "type": "array", "required": false, "items": { "type": "object", "fields": { "id": { "type": "string", "required": true } } } } ``` ### Нормализация protobuf-структур Для protobuf -> schema bridge дополнительно фиксируются такие правила: - `repeated` поле преобразуется в `array`; - `enum` преобразуется в `type: enum` со списком `enum_values`; - `map` преобразуется в `array` объектов `{ key, value }`; - `oneof` преобразуется в `type: oneof`, где каждый вариант представлен объектом с одним допустимым полем. ## 6. `MappingSet` и `MappingRule` `MappingSet` - набор правил преобразования между внутренним MCP input/output и protocol-specific request/response model. ### `MappingSet` ```json { "rules": [ { "source": "$.mcp.user_id", "target": "$.request.path.userId" } ] } ``` ### `MappingRule` Поля: - `source` - `JSONPath` в исходном контексте. - `target` - `JSONPath` в целевом контексте. - `required` - обязательно ли правило для корректного вызова. - `default_value` - значение по умолчанию. - `transform` - встроенное преобразование. - `condition` - условие применения правила. - `notes` - служебное описание для UI. Пример: ```json { "source": "$.mcp.profile.email", "target": "$.request.body.contact.email", "required": true, "default_value": null, "transform": { "kind": "identity" }, "condition": null, "notes": "Map email to CRM contact payload" } ``` ### Контексты `source` и `target` Для input mapping: - `$.mcp.*` - `$.request.path.*` - `$.request.query.*` - `$.request.headers.*` - `$.request.body.*` - `$.request.variables.*` - `$.request.grpc.*` Для output mapping: - `$.response.body.*` - `$.response.data.*` - `$.response.grpc.*` - `$.output.*` Для MVP достаточно управляемого подмножества `JSONPath`: - путь всегда начинается с фиксированного root context; - дальше используются dot-separated поля; - для массивов допускаются numeric indexes вида `[0]`; - quoted selectors, filter expressions и произвольные функции не поддерживаются. ### `Transform` Для MVP transformations должны быть ограничены: - `identity` - `to_string` - `to_number` - `to_boolean` - `join` - `split` - `wrap_array` - `unwrap_singleton` Пример: ```json { "kind": "to_string" } ``` ### Черновая генерация mapping Для MVP generation draft mapping может опираться на простое правило: - система сопоставляет уникальные leaf-поля с одинаковыми именами в source sample и target sample; - неоднозначные совпадения автоматически не связываются; - результат всегда остается черновиком и требует ручной проверки оператором. ## 7. `ExecutionConfig` `ExecutionConfig` задает параметры выполнения operation. ```json { "timeout_ms": 10000, "retry_policy": { "max_attempts": 1 }, "auth_profile_ref": "auth_01hr7x8rj2d8nq8v0c4m4t1r9e", "headers": { "X-Client": "crank" }, "protocol_options": { "rest": null, "graphql": null, "grpc": { "use_tls": true } } } ``` Поля: - `timeout_ms` - `retry_policy` - `auth_profile_ref` - `headers` - `protocol_options` Важно: - здесь хранятся execution settings, а не описание бизнес-схемы; - `protocol_options` должны оставаться узкими и протокол-специфичными; - секреты не хранятся внутри operation, только ссылки на secret store или auth profile. ## 8. `AuthProfile` `AuthProfile` - переиспользуемая конфигурация аутентификации для внешних вызовов. Operation ссылается на auth profile через `auth_profile_ref`, а не хранит секреты внутри себя. ### Базовая форма ```json { "id": "auth_01hr7x8rj2d8nq8v0c4m4t1r9e", "name": "crm-prod-bearer", "kind": "bearer", "config": { "header_name": "Authorization", "secret_ref": "secret://auth/crm-prod-token" }, "created_at": "2026-03-25T08:00:00Z", "updated_at": "2026-03-25T08:10:00Z" } ``` ### Поддерживаемые виды - `bearer` - `basic` - `api_key_header` - `api_key_query` ### MVP-решение по секретам Для MVP секреты должны храниться не в open text внутри operation version, а в отдельном secret storage слое. Рекомендуемое решение: - логическая ссылка в формате `secret://...`; - реальное значение хранится в приложении либо в зашифрованном хранилище, либо в env-backed secret store; - в документации и YAML export секреты всегда представляются только через `secret_ref`. ## 9. `ToolDescription` `ToolDescription` задает MCP-представление operation. ```json { "title": "Get customer profile", "description": "Returns a customer profile by external customer identifier.", "tags": ["crm", "customer"], "examples": [ { "input": { "customer_id": "123" } } ] } ``` Поля: - `title` - `description` - `tags` - `examples` ## 10. `Samples` `Samples` связывает operation с загруженными артефактами, на основе которых может быть построен черновик. ```json { "input_json_sample_ref": "file_01hr7xj7z4k1t0qm0cxsw6f1wx", "output_json_sample_ref": "file_01hr7xkvt3m1p6ms3m7r2dr8wz", "proto_file_ref": null, "descriptor_ref": null } ``` Поля: - `input_json_sample_ref` - `output_json_sample_ref` - `proto_file_ref` - `descriptor_ref` Примечания: - для REST чаще всего используются входной и выходной JSON samples; - для GraphQL чаще всего полезен sample ответа; - для gRPC основным artifact остается `.proto` или descriptor set, но input/output JSON samples тоже могут использоваться для MCP-facing модели. ## 11. `GeneratedDraft` `GeneratedDraft` хранит результат автоматической генерации схем и mappings. ```json { "status": "available", "source_types": ["input_json_sample", "output_json_sample"], "generated_at": "2026-03-25T08:05:00Z", "input_schema_generated": true, "output_schema_generated": true, "input_mapping_generated": true, "output_mapping_generated": true, "warnings": [ "Field $.response.body.meta was not mapped automatically" ] } ``` Поля: - `status` - `none`, `available`, `stale`, `failed` - `source_types` - `generated_at` - `input_schema_generated` - `output_schema_generated` - `input_mapping_generated` - `output_mapping_generated` - `warnings` Важно: - generated draft - это не runtime-источник истины; - runtime использует только сохраненные `input_schema`, `output_schema`, `input_mapping`, `output_mapping`; - generated draft нужен как вспомогательный слой для UI и ускорения конфигурирования. ## 12. Runtime view Для исполнения operation должно существовать упрощенное runtime-представление без UI-специфики. ```json { "id": "op_01hr7w0m6p8x9z4n7s2k3q5t6u", "protocol": "rest", "target": { "kind": "rest", "base_url": "https://api.example.com", "method": "POST", "path_template": "/v1/leads" }, "input_schema": { "type": "object", "fields": {} }, "output_schema": { "type": "object", "fields": {} }, "input_mapping": { "rules": [] }, "output_mapping": { "rules": [] }, "execution_config": { "timeout_ms": 10000 } } ``` Runtime view должно: - не содержать UI draft metadata; - не зависеть от raw uploaded files; - быть готовым к немедленному исполнению адаптером. ## 13. YAML-конфигурация Для импорта и экспорта система должна поддерживать `YAML`-представление operation. Принцип: - внутренняя доменная модель одна; - `JSON` и `YAML` - это два способа сериализации одной и той же конфигурации; - runtime не зависит от конкретного формата файла; - `YAML` нужен для переносимости и ручного редактирования. ### Базовая структура YAML ```yaml format_version: "1" kind: operation operation: id: op_01hr7w0m6p8x9z4n7s2k3q5t6u name: crm_create_lead display_name: Create Lead protocol: rest status: draft version: 3 target: kind: rest base_url: https://api.example.com method: POST path_template: /v1/leads input_schema: type: object fields: name: type: string required: true email: type: string required: true output_schema: type: object fields: id: type: string required: true status: type: string required: true input_mapping: rules: - source: $.mcp.name target: $.request.body.name - source: $.mcp.email target: $.request.body.email output_mapping: rules: - source: $.response.body.id target: $.output.id - source: $.response.body.status target: $.output.status execution_config: timeout_ms: 10000 auth_profile_ref: auth_01hr7x8rj2d8nq8v0c4m4t1r9e tool_description: title: Create CRM lead description: Creates a new lead in CRM by name and email. ``` ### Требования к YAML import/export - формат должен быть детерминированным; - структура должна быть человекочитаемой; - импорт должен валидировать схему, mapping и protocol-specific target; - экспорт не должен включать секреты в открытом виде; - ссылки на внешние артефакты допустимы, но режим экспорта должен быть явным. ### Режимы экспорта Минимально стоит предусмотреть два режима: - `portable` - экспорт только конфигурации operation и ссылок на внешние артефакты; - `bundle` - экспорт конфигурации operation вместе с вложенными sample metadata и descriptor metadata, если это допустимо. Для MVP можно начать только с `portable`. ## 14. Что важно не допустить - одну гигантскую `Operation`, в которой protocol-specific поля лежат вперемешку; - неявный mapping, который не сохраняется после генерации черновика; - смешивание uploaded artifacts и runtime-ready configuration; - хранение секретов внутри operation; - хранение реальных auth credentials внутри YAML export; - произвольные пользовательские скрипты в mapping; - YAML-экспорт, который становится отдельной несовместимой моделью по отношению к доменной структуре. ## 15. Практический итог Эта модель задает основу для: - Rust structs в `crank-core`, `crank-schema`, `crank-mapping`; - DTO для `admin-api`; - таблиц `operations`, `operation_versions`, `operation_samples`, `operation_descriptors`; - import/export layer для `YAML` конфигураций; - runtime view, который будет передаваться в `crank-runtime`. Следующим логическим шагом после этого документа должна стать схема БД, в которой эти сущности будут разложены по таблицам и связям.