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

## 1. Назначение документа

Этот документ фиксирует целевую формальную модель данных платформы. Его цель - определить такие структуры, которые можно без существенных изменений перенести в:

- Rust domain types;
- HTTP DTO;
- структуру таблиц БД;
- runtime-представление операций и агентов;
- UI-формы и конфигурационные экраны.

## 2. Общие принципы модели

### 2.1. Одна операция - один интеграционный контракт

Каждая `Operation` соответствует одному интеграционному контракту:

- GraphQL -> один конкретный `query` или `mutation`;
- gRPC -> один unary method или один server-streaming method в bounded execution mode;
- REST -> один endpoint-сценарий.

Однако MCP tool публикуется не напрямую из operation, а через `AgentOperationBinding` внутри конкретного `Agent`.

### 2.2. Внутренний транспортный формат - JSON

Независимо от внешнего протокола внутри системы данные представлены в JSON-ориентированном виде.

### 2.3. Mapping всегда явный

Даже если система умеет строить черновой mapping по примерам данных, итоговая конфигурация mapping сохраняется явно.

### 2.4. JSONPath как единый язык адресации

Для input и output mapping используется `JSONPath`.

### 2.5. Workspace - обязательная граница данных

Все продуктовые сущности принадлежат одному `Workspace`.

Минимальный набор workspace-scoped сущностей:

- `Secret`
- `SecretVersion`
- `Operation`
- `OperationVersion`
- `AuthProfile`
- `Agent`
- `AgentKey`
- `InvocationLog`
- `UsageRollup`

### 2.6. YAML как формат обмена конфигурацией

Помимо канонической JSON-модели система поддерживает импорт и экспорт конфигураций в `YAML`.

### 2.7. Streaming operation обязана быть bounded

Если операция использует upstream streaming, она должна работать в одном из execution modes:

- `window`
- `session`
- `async_job`

Бесконечный passthrough stream не является допустимой моделью `Operation`.

### 2.8. Execution model важнее transport-specific особенностей

Каждая операция в системе определяется двумя измерениями:

- `protocol`
- `execution_mode`

Это позволяет описывать:

- REST unary;
- REST SSE window;
- gRPC server-stream session;
- WebSocket event feed;
- SOAP request-response;

в рамках одной общей модели runtime и MCP publishing.

### 2.9. Уровень защиты задается операцией

Обязательная политика машинного доступа задается на уровне `Operation`, а не на уровне `Agent`.

Допустимые значения:

- `standard`
- `elevated`
- `strict`

Принцип:

- если операция требует более строгий режим доступа, агент не может его ослабить;
- один агент может публиковать операции с разной критичностью данных;
- `mcp-server` сравнивает тип представленного credential с требуемым `security_level`.

## 3. Корневые сущности

### 3.1. `Workspace`

Поля:

- `id`
- `slug`
- `display_name`
- `status`
- `settings`
- `created_at`
- `updated_at`

Назначение:

- логическая изоляция команд;
- scoping для операций, агентов, ключей и логов;
- основа для multi-tenant MCP endpoints.

### 3.2. `Operation`

Поля:

- `id`
- `workspace_id`
- `name`
- `display_name`
- `category`
- `protocol`
- `status`
- `version`
- `target`
- `input_schema`
- `output_schema`
- `input_mapping`
- `output_mapping`
- `execution_config`
- `security_level`
- `tool_description`
- `samples`
- `generated_draft`
- `config_export`
- `streaming_config`
- `created_at`
- `updated_at`
- `published_at`

### 3.3. `Agent`

`Agent` - пользовательская MCP-поверхность, которая собирает ограниченный набор published operations.

Поля:

- `id`
- `workspace_id`
- `slug`
- `display_name`
- `description`
- `status`
- `current_draft_version`
- `latest_published_version`
- `created_at`
- `updated_at`
- `published_at`

### 3.4. `AgentVersion`

Снимок конфигурации агента.

Поля:

- `agent_id`
- `version`
- `status`
- `instructions`
- `tool_selection_policy`
- `bindings`
- `created_at`

### 3.5. `AgentOperationBinding`

Связь published operation с agent version.

Поля:

- `operation_id`
- `operation_version`
- `tool_name`
- `tool_title`
- `tool_description_override`
- `enabled`

### 3.6. `Secret`

Секрет для доступа к внешней системе.

Поля:

- `id`
- `workspace_id`
- `name`
- `kind`
- `status`
- `current_version`
- `created_at`
- `updated_at`

Значение:

- plaintext не возвращается в list/get endpoints;
- текущее значение хранится в зашифрованном виде через `SecretVersion`;
- rotate создает новую версию секрета без потери ссылочной целостности.

### 3.7. `SecretVersion`

Зашифрованное значение секрета.

Поля:

- `secret_id`
- `version`
- `ciphertext`
- `key_version`
- `created_at`

### 3.8. `AuthProfile`

Используется только для доступа к внешним системам.

Поля:

- `id`
- `workspace_id`
- `name`
- `kind`
- `config`

Принцип:

- `AuthProfile` не хранит plaintext;
- config ссылается на `secret_id` или пару `secret_id`, если auth-схема составная;
- runtime применяет profile к запросу только в момент вызова upstream.

### 3.9. `AgentKey`

Отдельная сущность для машинного доступа к инструментам одного конкретного AI-агента.

Поля:

- `id`
- `workspace_id`
- `agent_id`
- `name`
- `prefix`
- `scopes`
- `status`
- `created_at`
- `last_used_at`
- `expires_at`

Секрет:

- полное значение ключа показывается только один раз при создании;
- в persistent storage сохраняется только `secret_hash`;
- ключ не должен использоваться как основной рабочий токен вызова в целевой защищенной модели;
- ключ используется для выпуска короткоживущего токена доступа.

### 3.10. `IssuedAgentToken`

Учет выданных машинных токенов для вызова MCP-инструментов.

Поля:

- `id`
- `workspace_id`
- `agent_id`
- `agent_key_id`
- `platform_client_id`
- `token_kind`
- `status`
- `scopes`
- `max_uses`
- `used_count`
- `cnf_jkt`
- `expires_at`
- `created_at`
- `used_at`

Принцип:

- токен живет ограниченное время;
- токен может быть одноразовым;
- токен может быть привязан к ключевой паре клиента;
- токен выражает более узкие права, чем длинноживущий ключ агента.

### 3.11. `User`

Поля:

- `id`
- `email`
- `display_name`
- `password_hash`
- `status`
- `created_at`

Пароль:

- хранится только как `Argon2id` hash;
- plaintext пароль не сохраняется;
- верификация использует `password_pepper` из env.

### 3.11. `UserSession`

Поля:

- `id`
- `user_id`
- `secret_hash`
- `status`
- `expires_at`
- `last_seen_at`
- `created_at`

Секрет:

- браузеру выдается только opaque session token;
- в persistent storage сохраняется только `secret_hash`;
- подпись и верификация используют `session_secret` из env.

### 3.12. `Membership`

Поля:

- `workspace_id`
- `user_id`
- `role`
- `created_at`

### 3.13. `InvitationToken`

Поля:

- `id`
- `workspace_id`
- `email`
- `role`
- `status`
- `expires_at`
- `created_at`

Токен:

- полный invite token показывается только один раз при создании;
- в persistent storage сохраняется только `token_hash`.

### 3.14. `InvocationLog`

Продуктовая запись о вызове tool.

Поля:

- `id`
- `workspace_id`
- `agent_id`
- `operation_id`
- `request_id`
- `level`
- `status`
- `duration_ms`
- `error_kind`
- `request_preview`
- `response_preview`
- `created_at`

### 3.15. `UsageRollup`

Агрегированная статистика по периоду.

Поля:

- `workspace_id`
- `agent_id`
- `operation_id`
- `period_kind`
- `period_start`
- `calls_total`
- `calls_ok`
- `calls_error`
- `p50_ms`
- `p95_ms`
- `p99_ms`

### 3.16. `StreamSession`

Поля:

- `id`
- `workspace_id`
- `agent_id`
- `operation_id`
- `mode`
- `status`
- `cursor`
- `state`
- `expires_at`
- `last_poll_at`
- `created_at`
- `closed_at`

Назначение:

- хранение bounded session state для streaming tools;
- поддержка `start/poll/stop`;
- cleanup orphaned и expired sessions.

### 3.17. `AsyncJobHandle`

Поля:

- `id`
- `workspace_id`
- `agent_id`
- `operation_id`
- `status`
- `progress`
- `result`
- `error`
- `expires_at`
- `created_at`
- `updated_at`
- `finished_at`

Назначение:

- поддержка `start/status/result/cancel` для long-running upstream actions.

## 4. `Target`

`Target` описывает конкретный внешний вызов. Это discriminated union по протоколу.

### 4.1. `RestTarget`

- `kind`
- `base_url`
- `method`
- `path_template`
- `static_headers`

### 4.2. `GraphqlTarget`

- `kind`
- `endpoint`
- `operation_type`
- `operation_name`
- `query_template`
- `response_path`

### 4.3. `GrpcTarget`

- `kind`
- `server_addr`
- `package`
- `service`
- `method`
- `read_only`
- `descriptor_ref`
- `descriptor_set_b64`

### 4.4. `WebSocketTarget`

- `kind`
- `url`
- `subprotocols`
- `subscribe_message_template`
- `unsubscribe_message_template`
- `static_headers`

### 4.5. `SoapTarget`

- `kind`
- `wsdl_ref`
- `service_name`
- `port_name`
- `operation_name`
- `endpoint_override`
- `soap_version`
- `soap_action`
- `binding_style`
- `headers`
- `fault_contract`
- `metadata`

## 5. `ProtocolOptions`

`ProtocolOptions` хранит protocol-specific runtime tuning, который не должен смешиваться с общей `ExecutionConfig`.

### 5.1. `GrpcProtocolOptions`

- `use_tls`

### 5.2. `WebsocketProtocolOptions`

- `heartbeat_interval_ms`
- `reconnect_max_attempts`
- `reconnect_backoff_ms`

### 5.3. `SoapProtocolOptions`

- `use_tls`
- `validate_certificate`
- `ws_security_profile`

## 6. `ExecutionConfig`

`ExecutionConfig` хранит общие runtime-настройки операции.

Ключевые поля:

- `timeout_ms`
- `retry_policy`
- `response_cache`
- `auth_profile_ref`
- `headers`
- `protocol_options`
- `streaming`

### 6.1. `ResponseCachePolicy`

Первый поддержанный вариант response cache policy:

- `ttl_ms`

Ограничения стартовой реализации:

- policy задается явно на операции;
- response cache допускается только для read-only вызовов;
- текущие поддержанные пути:
  - `REST GET`
  - `GraphQL query`
  - `gRPC unary` только при `GrpcTarget.read_only = true`
  - все варианты только без `auth_profile_ref`
- операции со `streaming` не допускают response cache даже при наличии policy;
- cache key должен строиться не глобально, а минимум в контексте:
  - `workspace`
  - `agent`
  - `operation`
  - `operation version`
  - `request fingerprint`

Это позволяет избежать неявного кэширования ответов, зависящих от upstream credentials.

## 7. XML normalization model

Для SOAP/XSD-пайплайна `crank-schema` теперь фиксирует XML-origin metadata отдельными code-level types:

- `XmlNodeKind`
- `XmlQualifiedName`
- `XmlSchemaBinding`

Они описывают:

- является ли узел element / attribute / text;
- локальное имя и namespace;
- repeated / nillable semantics.

## 8. `Schema`

`Schema` - нормализованное описание входа или выхода.

Поддерживаются:

- скалярные поля;
- вложенные объекты;
- массивы;
- enum;
- nullable-поля;
- `oneof` для protobuf.

## 9. Принцип совместимости

Если UI требует сущность, которой нет в текущем backend, эта сущность должна быть сначала явно добавлена в эту модель данных, а уже потом в код и БД.

Для upstream credentials это означает:

- placeholder-строки вида `${secrets.API_KEY}` не считаются реальной моделью данных;
- рабочая продуктовая модель строится только через `Secret` + `AuthProfile`.

Для `Operations` и `Wizard` дополнительный уровень контрактной детализации закреплен в:

- `docs/operations-workspace-contracts.md`
