608 lines
13 KiB
Markdown
608 lines
13 KiB
Markdown
# 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}/stream-test-runs`
|
||
|
||
Назначение:
|
||
|
||
- выполнить `window`, `session` или `async_job` test-run для draft version;
|
||
- показать оператору runtime behavior до publish.
|
||
|
||
Тело:
|
||
|
||
```json
|
||
{
|
||
"version": 4,
|
||
"input": {
|
||
"service": "billing",
|
||
"level": "error"
|
||
},
|
||
"test_mode": "window",
|
||
"overrides": {
|
||
"streaming": {
|
||
"window_duration_ms": 3000,
|
||
"max_items": 50
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
Успех для `window`:
|
||
|
||
```json
|
||
{
|
||
"run_id": "strun_01j0test",
|
||
"mode": "window",
|
||
"status": "completed",
|
||
"window_complete": true,
|
||
"truncated": false,
|
||
"has_more": false,
|
||
"summary": {},
|
||
"items": [],
|
||
"cursor": null,
|
||
"duration_ms": 2871
|
||
}
|
||
```
|
||
|
||
Успех для `session`:
|
||
|
||
```json
|
||
{
|
||
"run_id": "strun_01j0test",
|
||
"mode": "session",
|
||
"status": "running",
|
||
"session": {
|
||
"session_id": "sess_01j0stream",
|
||
"expires_at": "2026-04-06T12:05:00Z",
|
||
"poll_after_ms": 2000
|
||
},
|
||
"preview": {
|
||
"summary": {},
|
||
"items": []
|
||
}
|
||
}
|
||
```
|
||
|
||
Успех для `async_job`:
|
||
|
||
```json
|
||
{
|
||
"run_id": "strun_01j0test",
|
||
"mode": "async_job",
|
||
"status": "running",
|
||
"job": {
|
||
"job_id": "job_01j0deploy",
|
||
"status": "running",
|
||
"progress": {
|
||
"pct": 12
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### `POST /api/admin/workspaces/{workspace_id}/operations/{operation_id}/stream-test-runs/{run_id}/poll`
|
||
|
||
Назначение:
|
||
|
||
- продолжить session-oriented test-run.
|
||
|
||
Тело:
|
||
|
||
```json
|
||
{
|
||
"session_id": "sess_01j0stream"
|
||
}
|
||
```
|
||
|
||
Ответ:
|
||
|
||
```json
|
||
{
|
||
"run_id": "strun_01j0test",
|
||
"mode": "session",
|
||
"status": "running",
|
||
"window_complete": false,
|
||
"truncated": false,
|
||
"has_more": true,
|
||
"summary": {},
|
||
"items": [],
|
||
"cursor": "next_cursor"
|
||
}
|
||
```
|
||
|
||
### `POST /api/admin/workspaces/{workspace_id}/operations/{operation_id}/stream-test-runs/{run_id}/stop`
|
||
|
||
Назначение:
|
||
|
||
- остановить test session.
|
||
|
||
### `GET /api/admin/workspaces/{workspace_id}/operations/{operation_id}/stream-test-runs/{run_id}/result`
|
||
|
||
Назначение:
|
||
|
||
- получить финальный результат async-job test-run.
|
||
|
||
## 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.
|