# Streaming Admin API

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

Этот документ фиксирует точные HTTP-контракты для потоковой модели Crank.

Он дополняет:

- [admin-api.md](/home/a.tolmachev/code/rust/mcpaas/docs/admin-api.md)
- [streaming-mcp-plan.md](/home/a.tolmachev/code/rust/mcpaas/docs/streaming-mcp-plan.md)
- [streaming-ui-contract.md](/home/a.tolmachev/code/rust/mcpaas/docs/streaming-ui-contract.md)
- [streaming-runtime-design.md](/home/a.tolmachev/code/rust/mcpaas/docs/streaming-runtime-design.md)

Цель документа:

- зафиксировать DTO;
- зафиксировать route groups;
- зафиксировать валидацию;
- зафиксировать expected error model;
- зафиксировать page-to-endpoint contract для streaming configuration и test-runs.

## 2. Общие принципы

- все ресурсы являются `workspace-scoped`;
- streaming configuration является частью `operation version`;
- `session` и `async_job` state не редактируются напрямую из UI;
- test-runs могут создавать временные sessions и jobs, но не публикуют их как runtime resources;
- transport errors и validation errors разделяются;
- лимиты и safety-параметры валидируются на сервере, а не только в UI.

Базовый префикс:

```text
/api/admin/workspaces/{workspace_id}
```

## 3. Основные ресурсы

- `streaming-presets`
- `streaming-validation`
- `stream-test-runs`
- `stream-sessions`
- `async-jobs`
- `protocol-capabilities`

## 4. Общие DTO

### 4.1. `ExecutionMode`

```json
{
  "mode": "unary"
}
```

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

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

### 4.2. `StreamingConfig`

```json
{
  "mode": "window",
  "transport_behavior": "server_stream",
  "window_duration_ms": 5000,
  "poll_interval_ms": 2000,
  "upstream_timeout_ms": 10000,
  "idle_timeout_ms": 30000,
  "max_session_lifetime_ms": 300000,
  "max_items": 200,
  "max_bytes": 131072,
  "aggregation_mode": "summary_plus_samples",
  "summary_path": "$.summary",
  "items_path": "$.items",
  "cursor_path": "$.cursor",
  "status_path": "$.status",
  "done_path": "$.done",
  "redacted_paths": [
    "$.items[*].token",
    "$.summary.secret"
  ],
  "truncate_item_fields": true,
  "max_field_length": 512,
  "drop_duplicates": true,
  "sampling_rate": 1.0,
  "tool_family": {
    "start_tool_name": "cluster_events_start",
    "poll_tool_name": "cluster_events_poll",
    "stop_tool_name": "cluster_events_stop",
    "status_tool_name": "deploy_status",
    "result_tool_name": "deploy_result",
    "cancel_tool_name": "deploy_cancel"
  }
}
```

### 4.3. `StreamingValidationError`

```json
{
  "code": "streaming_validation_error",
  "message": "Streaming configuration is invalid",
  "details": [
    {
      "field": "window_duration_ms",
      "reason": "must_be_positive"
    },
    {
      "field": "max_items",
      "reason": "must_not_exceed_workspace_limit"
    }
  ]
}
```

### 4.4. `ProtocolCapability`

```json
{
  "protocol": "grpc",
  "supports_execution_modes": [
    "unary",
    "window",
    "session",
    "async_job"
  ],
  "supports_transport_behaviors": [
    "request_response",
    "server_stream"
  ],
  "supports_auth_kinds": [
    "none",
    "bearer",
    "basic",
    "api_key_header",
    "api_key_query"
  ],
  "supports_upload_artifacts": [
    "proto",
    "descriptor_set"
  ],
  "supports_cursor_path": true,
  "supports_done_path": true,
  "supports_aggregation_mode": [
    "raw_items",
    "summary_only",
    "summary_plus_samples",
    "stats",
    "latest_state"
  ]
}
```

## 5. Capabilities endpoints

### `GET /api/admin/workspaces/{workspace_id}/protocol-capabilities`

Назначение:

- отдать UI полную capability matrix;
- убрать protocol-specific hardcode из frontend.

Ответ:

```json
{
  "items": [
    {
      "protocol": "rest",
      "supports_execution_modes": ["unary", "window", "session", "async_job"],
      "supports_transport_behaviors": ["request_response", "server_stream"],
      "supports_auth_kinds": ["none", "bearer", "basic", "api_key_header", "api_key_query"],
      "supports_upload_artifacts": [],
      "supports_cursor_path": true,
      "supports_done_path": true,
      "supports_aggregation_mode": ["raw_items", "summary_only", "summary_plus_samples", "stats", "latest_state"]
    }
  ]
}
```

## 6. Streaming validation endpoints

### `POST /api/admin/workspaces/{workspace_id}/streaming/validate`

Назначение:

- проверить streaming config до сохранения operation;
- вернуть protocol-aware ошибки.

Тело:

```json
{
  "protocol": "websocket",
  "target": {},
  "execution_config": {
    "streaming": {}
  }
}
```

Успех:

```json
{
  "valid": true,
  "warnings": [
    {
      "field": "max_bytes",
      "code": "may_truncate_large_event_payloads",
      "message": "Large event payloads may be truncated"
    }
  ]
}
```

Ошибка:

```json
{
  "valid": false,
  "errors": [
    {
      "field": "transport_behavior",
      "code": "unsupported_transport_behavior"
    }
  ]
}
```

## 7. Streaming presets endpoints

### `GET /api/admin/workspaces/{workspace_id}/streaming-presets`

Назначение:

- отдать рекомендованные UI presets.

Ответ:

```json
{
  "items": [
    {
      "preset_id": "logs_window_5s",
      "display_name": "Logs Window 5s",
      "protocols": ["rest", "grpc", "websocket"],
      "streaming": {
        "mode": "window",
        "window_duration_ms": 5000,
        "max_items": 100,
        "max_bytes": 65536,
        "aggregation_mode": "summary_plus_samples"
      }
    }
  ]
}
```

## 8. Streaming operation test-runs

### `POST /api/admin/workspaces/{workspace_id}/operations/{operation_id}/test-runs`

Назначение:

- выполнить `unary`, `window`, `session` или `async_job` test-run для draft version;
- показать оператору runtime behavior до publish.

Тело:

```json
{
  "version": 4,
  "input": {
    "service": "billing",
    "level": "error"
  },
  "version": 4,
  "input": {
    "service": "billing",
    "level": "error"
  }
}
```

Успех для `unary`:

```json
{
  "ok": true,
  "mode": "unary",
  "request_preview": {
    "service": "billing",
    "level": "error"
  },
  "response_preview": {
    "summary": "completed"
  },
  "errors": [],
  "window": null,
  "stream_session": null,
  "async_job": null
}
```

Успех для `window`:

```json
{
  "ok": true,
  "mode": "window",
  "request_preview": {
    "service": "billing",
    "level": "error"
  },
  "response_preview": {
    "summary": {},
    "items": []
  },
  "errors": [],
  "window": {
    "window_complete": true,
    "truncated": false,
    "has_more": false,
    "cursor": null
  },
  "stream_session": null,
  "async_job": null
}
```

Успех для `session`:

```json
{
  "ok": true,
  "mode": "session",
  "request_preview": {
    "service": "billing",
    "level": "error"
  },
  "response_preview": {
    "summary": {},
    "items": []
  },
  "errors": [],
  "window": null,
  "stream_session": {
    "session_id": "sess_01j0stream",
    "status": "running",
    "expires_at": "2026-04-06T12:05:00Z",
    "poll_after_ms": 2000,
    "preview": {
      "summary": {},
      "items": []
    }
  },
  "async_job": null
}
```

Успех для `async_job`:

```json
{
  "ok": true,
  "mode": "async_job",
  "request_preview": {
    "service": "billing",
    "level": "error"
  },
  "response_preview": {
    "job": "started"
  },
  "errors": [],
  "window": null,
  "stream_session": null,
  "async_job": {
    "job_id": "job_01j0deploy",
    "status": "running",
    "progress": {
      "pct": 12
    }
  }
}
```

Ошибка:

```json
{
  "ok": false,
  "mode": "window",
  "request_preview": {
    "service": "billing",
    "level": "error"
  },
  "response_preview": null,
  "errors": [
    {
      "code": "runtime_test_failure",
      "message": "upstream timeout"
    }
  ],
  "window": null,
  "stream_session": null,
  "async_job": null
}
```

Примечания:

- endpoint не использует отдельные `stream-test-runs/*` subresources;
- session и async-job test-runs создают обычные runtime resources в `stream_sessions` и `async_jobs`;
- дальнейшее наблюдение идет через `GET /stream-sessions`, `GET /stream-sessions/{session_id}`, `POST /stream-sessions/{session_id}/stop`, `GET /async-jobs`, `GET /async-jobs/{job_id}`, `GET /async-jobs/{job_id}/result`, `POST /async-jobs/{job_id}/cancel`.

## 9. Runtime session resources

### `GET /api/admin/workspaces/{workspace_id}/stream-sessions`

Назначение:

- список активных и недавних session resources для observability/debug UI.

Query params:

- `operation_id`
- `agent_id`
- `status`
- `page`
- `page_size`

Ответ:

```json
{
  "items": [
    {
      "id": "sess_01j0stream",
      "operation_id": "op_01j0",
      "agent_id": "agent_01j0",
      "mode": "session",
      "status": "running",
      "expires_at": "2026-04-06T12:05:00Z",
      "last_poll_at": "2026-04-06T12:00:10Z",
      "created_at": "2026-04-06T12:00:00Z"
    }
  ],
  "page": 1,
  "page_size": 20,
  "total": 1
}
```

### `GET /api/admin/workspaces/{workspace_id}/stream-sessions/{session_id}`

Назначение:

- detail session metadata;
- без возврата полного raw state.

### `POST /api/admin/workspaces/{workspace_id}/stream-sessions/{session_id}/stop`

Назначение:

- административно остановить runtime session.

### `DELETE /api/admin/workspaces/{workspace_id}/stream-sessions/{session_id}`

Назначение:

- hard cleanup завершенной session;
- доступен только для `stopped`, `failed`, `expired`.

## 10. Async job resources

### `GET /api/admin/workspaces/{workspace_id}/async-jobs`

Назначение:

- список активных и недавних job resources.

### `GET /api/admin/workspaces/{workspace_id}/async-jobs/{job_id}`

Назначение:

- status/progress/result metadata.

### `POST /api/admin/workspaces/{workspace_id}/async-jobs/{job_id}/cancel`

Назначение:

- административная отмена long-running job.

### `GET /api/admin/workspaces/{workspace_id}/async-jobs/{job_id}/result`

Назначение:

- получить финальный нормализованный результат.

## 11. Integration into operation contracts

`streaming` не является отдельным top-level resource для опубликованной операции. Он живет внутри:

- `OperationVersionDocument.execution_config.streaming`

и входит в:

- `POST /operations`
- `PATCH /operations/{operation_id}`
- `POST /operations/{operation_id}/versions`
- `GET /operations/{operation_id}/versions/{version}`

### 11.1. `OperationVersionDocument.execution_config.streaming`

```json
{
  "execution_config": {
    "timeout_ms": 10000,
    "retries": 0,
    "auth_profile_ref": "auth_profile_01j0",
    "streaming": {
      "mode": "window",
      "transport_behavior": "server_stream",
      "window_duration_ms": 5000,
      "upstream_timeout_ms": 10000,
      "max_items": 100,
      "max_bytes": 65536,
      "aggregation_mode": "summary_plus_samples",
      "summary_path": "$.summary",
      "items_path": "$.items",
      "cursor_path": "$.cursor",
      "status_path": "$.status",
      "done_path": "$.done",
      "redacted_paths": [],
      "truncate_item_fields": true,
      "max_field_length": 512,
      "drop_duplicates": true,
      "sampling_rate": 1.0,
      "tool_family": {}
    }
  }
}
```

## 12. Validation rules

### 12.1. Общие

- `mode=unary` запрещает `tool_family`;
- `max_items` > 0;
- `max_bytes` > 0;
- `window_duration_ms` > 0 для `window`;
- `idle_timeout_ms` обязателен для `session`;
- `max_session_lifetime_ms` обязателен для `session`;
- `tool_family.start_tool_name/poll_tool_name/stop_tool_name` обязательны для `session`;
- `tool_family.start_tool_name/status_tool_name/result_tool_name/cancel_tool_name` обязательны для `async_job`.

### 12.2. Protocol-aware

REST:

- `transport_behavior=server_stream` допустим только при streaming-capable target.

GraphQL:

- `window`, `session`, `async_job` пока запрещены;
- `subscription` невалиден как target type.

gRPC:

- `transport_behavior=server_stream` допустим только для server-streaming method.

WebSocket:

- `mode=unary` запрещен;
- требуется `subscribe_message_template` для `session` и `window`.

SOAP:

- `mode=session` запрещен в первой волне;
- `mode=window` допустим только при наличии polling-style status operation family;
- `mode=async_job` требует status/result contract.

## 13. Error model

Коды:

- `streaming_validation_error`
- `unsupported_execution_mode`
- `unsupported_transport_behavior`
- `stream_window_timeout`
- `stream_window_truncated`
- `stream_session_expired`
- `stream_session_not_found`
- `async_job_not_found`
- `async_job_not_ready`
- `async_job_cancelled`
- `protocol_capability_mismatch`

### 13.1. Ошибка несовместимого protocol mode

```json
{
  "code": "protocol_capability_mismatch",
  "message": "websocket target does not support unary execution mode",
  "details": [
    {
      "field": "execution_config.streaming.mode",
      "reason": "unsupported_for_protocol"
    }
  ]
}
```

## 14. Route-to-service mapping

Ожидаемые service handlers в `apps/admin-api`:

- `list_protocol_capabilities`
- `list_streaming_presets`
- `validate_streaming_config`
- `start_stream_test_run`
- `poll_stream_test_run`
- `stop_stream_test_run`
- `get_stream_test_result`
- `list_stream_sessions`
- `get_stream_session`
- `stop_stream_session`
- `delete_stream_session`
- `list_async_jobs`
- `get_async_job`
- `cancel_async_job`
- `get_async_job_result`

Это не route names, а service-level функции orchestration.
