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

22 KiB
Raw Blame History

Модель данных

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

Пример

{
  "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

{
  "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

{
  "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

{
  "kind": "grpc",
  "server_addr": "https://grpc.example.com:443",
  "package": "crm.v1",
  "service": "LeadService",
  "method": "CreateLead",
  "descriptor_ref": "desc_01hr7yn4d6g1x6vwt7h9n0e7ab",
  "descriptor_set_b64": "<base64-encoded-descriptor-set>"
}

Поля:

  • 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.

Базовая форма

{
  "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

Модель поля

{
  "type": "string",
  "required": true,
  "nullable": false,
  "description": "User email",
  "default": null
}

Тип объекта:

{
  "type": "object",
  "required": true,
  "fields": {
    "email": {
      "type": "string",
      "required": true
    }
  }
}

Тип массива:

{
  "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<K, V> преобразуется в array объектов { key, value };
  • oneof преобразуется в type: oneof, где каждый вариант представлен объектом с одним допустимым полем.

6. MappingSet и MappingRule

MappingSet - набор правил преобразования между внутренним MCP input/output и protocol-specific request/response model.

MappingSet

{
  "rules": [
    {
      "source": "$.mcp.user_id",
      "target": "$.request.path.userId"
    }
  ]
}

MappingRule

Поля:

  • source - JSONPath в исходном контексте.
  • target - JSONPath в целевом контексте.
  • required - обязательно ли правило для корректного вызова.
  • default_value - значение по умолчанию.
  • transform - встроенное преобразование.
  • condition - условие применения правила.
  • notes - служебное описание для UI.

Пример:

{
  "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

Пример:

{
  "kind": "to_string"
}

Черновая генерация mapping

Для MVP generation draft mapping может опираться на простое правило:

  • система сопоставляет уникальные leaf-поля с одинаковыми именами в source sample и target sample;
  • неоднозначные совпадения автоматически не связываются;
  • результат всегда остается черновиком и требует ручной проверки оператором.

7. ExecutionConfig

ExecutionConfig задает параметры выполнения operation.

{
  "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, а не хранит секреты внутри себя.

Базовая форма

{
  "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.

{
  "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 с загруженными артефактами, на основе которых может быть построен черновик.

{
  "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.

{
  "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-специфики.

{
  "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

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.

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