Уточнить DSH, плагины, roadmap и контракты исполнения

This commit is contained in:
admin committed 2026-11-11 05:24:36 +00:00
1 parent 2af6dd78ba
commit 41a4397ed2
25 files changed
+743 -37

No files matched your search

+1 -1
View File
@@ -28,4 +28,4 @@ UpgradePreview: fromVersionId, toVersionId, changes:[{path, kind}], validation,
## Подключения
GET /v1/connections возвращает доступные ConnectionSummary (id, provider, label, grantedScopes, expiresAt), не credentials. POST /v1/connections создаёт подключение через provider authorization flow или защищённую передачу ключа; конкретные provider payload задаются отдельными адаптерами и не входят в универсальную config schema. DELETE /v1/connections/{id} отзывает платформенный доступ и при возможности выполняет revoke у провайдера. Повторное использование в новом Run после отзыва запрещено.
GET /v1/connections возвращает Page<ConnectionSummary> с cursor и limit, без credentials. Подключения создаются через provider authorization flow; единый контракт и защищённая передача API key описаны в [Provider API](provider-api.md). DELETE /v1/connections/{id} отзывает платформенный доступ и при возможности выполняет revoke у провайдера. Повторное использование в новом Run после отзыва запрещено.
+1 -1
View File
@@ -28,6 +28,6 @@ PATCH/PUT изменяемых ресурсов требуют If-Match, отв
## События и файлы
SSE endpoint принимает Last-Event-ID; event id монотонен в пределах Run. После retention слишком старый cursor возвращает 410 с указанием получить snapshot. Повторные события дедуплицируются по eventId. SSE не является источником истины о состоянии: GET Run возвращает канонический snapshot.
SSE endpoint принимает Last-Event-ID; SSE cursor монотонен в пределах Run и назначается сервером, отдельно от runner eventId. После retention слишком старый cursor возвращает 410 с указанием получить snapshot. Повторные runner events дедуплицируются по attemptId + eventId. SSE data содержит RunEvent, а transport id — серверный cursor; клиент хранит cursor для reconnect. SSE не является источником истины о состоянии: GET Run возвращает канонический snapshot.
Binary upload выполняется по scoped grant. Он содержит uploadId, url, expiresAt, maxBytes, requiredHeaders; постоянные записи хранят artifactId, не подписанный URL. Grant не считается подтверждением загрузки: finalize проверяет размер, digest и объект. По умолчанию ошибки не повторяются автоматически, кроме явных transient ошибок с ограниченным backoff и jitter.
+15 -5
View File
@@ -12,19 +12,19 @@ Capabilities: os, architecture, executionKinds:["oci"], features:["cancel-v1"],
| Метод и путь | Запрос | Ответ |
|---|---|---|
| POST /v1/runner-jobs/claim | runnerId, availableSlots, capabilitiesRevision | 200 Assignment или 204 |
| POST /v1/attempts/{id}/heartbeat | leaseEpoch, sequence, progress? | 200 {leaseUntil, cancelRequested} |
| POST /v1/runner-jobs/claim | runnerId, claimRequestId, availableSlots, capabilitiesRevision | 200 Assignment или 204 |
| POST /v1/attempts/{id}/heartbeat | leaseEpoch, sequence, progress? | 200 {leaseUntil, executionToken, tokenExpiresAt, cancelRequested} |
| POST /v1/attempts/{id}/started | leaseEpoch, sequence | 200 Attempt |
| POST /v1/attempts/{id}/events | leaseEpoch, events:[RunEvent] | 200 {acceptedThrough} |
| POST /v1/attempts/{id}/uploads | leaseEpoch, purpose, mediaType, sizeBytes, digest | 201 UploadGrant |
| POST /v1/attempts/{id}/complete | leaseEpoch, completionId, outcome, resultManifest?, error? | 200 Attempt |
| POST /v1/attempts/{id}/suspend | leaseEpoch, checkpointArtifactId, inputRequest | 200 Run |
Assignment: attemptId, runId, leaseEpoch, leaseUntil, executionSpec, executionToken. Execution token scoped к Attempt. Запрос claim authenticated идентичностью Runner; последующие операции — execution token. Назначение создаётся сервером атомарно; один claim выдаёт максимум один Attempt, availableSlots не разрешает превысить maxConcurrency пула.
Assignment: attemptId, runId, leaseEpoch, leaseUntil, executionSpec, attemptContext, executionToken, tokenExpiresAt. См. [схему](../contracts/runner-assignment.schema.json). Execution token scoped к Attempt. Запрос claim authenticated идентичностью Runner; последующие операции — execution token. Назначение создаётся сервером атомарно; один claim выдаёт максимум один Attempt, availableSlots не разрешает превысить maxConcurrency пула.
Outcome: succeeded/failed/cancelled. Success требует ResultManifest и сохранённых объектов, даже если artifacts пуст. Error: code, message, retryableHint; решение о retry принимает платформа. Complete повторяется с тем же completionId и телом; другой payload после terminal state → 409. Поздние heartbeat, события и complete с прежним epoch → 409 LEASE_EXPIRED. События могут доставляться повторно; уникальны по attemptId + eventId.
Outcome: succeeded/failed/cancelled. Success требует ResultManifest и сохранённых объектов, даже если artifacts пуст. Error: code, message, retryableHint; решение о retry принимает платформа. Complete повторяется с тем же completionId и телом; другой payload после terminal state → 409. Поздние heartbeat и события с прежним epoch → 409 LEASE_EXPIRED. Повтор уже принятого complete с тем же completionId и телом возвращает сохранённый ответ даже после истечения lease, но никогда не меняет состояние. Для этого replay допускается с действующей идентичностью назначенного Runner; новый terminal report с устаревшим epoch запрещён. События могут доставляться повторно; уникальны по attemptId + eventId.
Suspend допустим только для checkpoint-v1 + input-v1. InputRequest: id, schema, prompt, expiresAt. Платформа принимает checkpoint, переводит Run в waiting_input, отзывает lease; новый Attempt получает checkpointRef и проверенный ответ. Без checkpoint протокол не обещает сохранение памяти процесса.
Suspend допустим только для checkpoint-v1 + input-v1. InputRequest: id, schema, prompt, expiresAt. Платформа принимает checkpoint, переводит Run в waiting_input, отзывает lease; новый Attempt получает checkpointRef и проверенный ответ в AttemptContext, отдельно от ExecutionSpec. Без checkpoint протокол не обещает сохранение памяти процесса.
## Локальный процесс
@@ -35,3 +35,13 @@ Suspend допустим только для checkpoint-v1 + input-v1. InputRequ
## Минимальная проверка совместимости
Runner обязан показать: корректный success; failure; отказ при несовместимом spec; отмену; тайм-аут; повтор complete без дублей; отказ с просроченным lease; изоляцию файлов между задачами; корректный upload digest; отсутствие secrets в событиях. Resume проверяется только при соответствующей capability. Managed и customer-managed пулы отображаются раздельно.
## Восстановление потерянного ответа
claimRequestId уникален в пределах runnerId. Повтор claim с тем же ID и телом возвращает то же назначение (или исходный 204), не занимает новый слот. Дедупликация хранится 24 часа; перед повторным получением работы после reconnect Runner вызывает GET /v1/runners/{id}/assignments (200 {items:[Assignment]}) со своей идентичностью. Endpoint возвращает только активные назначения этого Runner и перевыпускает их короткие токены. Если lease уже утрачен, старую работу прекращают; восстановление не продлевает истёкший lease.
Heartbeat продлевает lease и executionToken не дальше deadlineAt. Complete ID и хеш тела сохраняются до удаления Run. Sequence монотонен внутри потока событий; heartbeat/started имеют отдельный счётчик controlSequence (поле sequence в этих запросах), чтобы доставка логов не блокировала lease. Повтор с тем же номером и другим телом — 409. Конфликт события по eventId либо sequence также даёт 409. Клиент сериализует control-запросы.
ResultManifest валидируется по [схеме](../contracts/result-manifest.schema.json), а output — дополнительно по outputSchemaRef шаблона. InputRequest schema использует тот же ограниченный профиль, что формы настроек. Событие input_requested информационное; только успешный suspend меняет состояние.
Повтор claim не возобновляет утраченную аренду: если сохранённое назначение уже неактивно, сервер возвращает 409 ASSIGNMENT_EXPIRED. Перед запуском из повторного ответа Runner проверяет leaseUntil по серверному времени. ClaimRequestId с иным телом возвращает 409. Поля runId в Assignment и вложенном ExecutionSpec должны совпадать; server validator проверяет это отдельно от JSON Schema.
+35
View File
@@ -0,0 +1,35 @@
# Provider API v1
Platform identity и provider identity независимы. Все операции требуют текущего actor и tenant; запрос к provider mode допускается только если матрица разрешает local/on_prem/managed_remote. Provider broker — модуль Platform API для управляемых credentials, локальный аналог — sidecar; provider plugin вызывается в изолированном host, а не внутри API-процесса.
| Метод и путь | Запрос | Ответ |
|---|---|---|
| GET /v1/providers | deploymentMode | 200 {items:[ProviderDescriptor]} |
| POST /v1/provider-auth-flows | providerId, methodId, placement, returnRoute | 201 AuthFlow |
| GET /v1/provider-auth-flows/{id} | — | 200 AuthFlow с текущим status |
| DELETE /v1/provider-auth-flows/{id} | — | 204, flow cancelled |
| GET /v1/provider-auth/callback/{providerId} | state, code или error | 303 на allowlisted внутренний returnRoute |
| GET /v1/connections/{id} | — | 200 ConnectionSummary |
| POST /v1/connections/{id}/verify | — | 200 {status, capabilities, checkedAt} |
| GET /v1/connections/{id}/models | cursor, limit | 200 Page<ModelDescriptor> |
| DELETE /v1/connections/{id} | — | 204 после платформенного revoke; remote revoke best effort с аудитом |
ProviderDescriptor: id, pluginVersionId, methods:[{id, kind:api_key/oauth_pkce/device_code/official_client, allowedPlacements, status:enabled/gated/unsupported, reason?}], capabilitiesRevision. methodId определяется provider adapter; браузер не выбирает произвольный OAuth endpoint.
AuthFlow: id, providerId, methodId, status:pending/connected/failed/expired/cancelled, expiresAt, action:{type:open_url/device_code/local_client, url?, userCode?, intervalSeconds?}, connectionId?. PKCE verifier, refresh token и client secret не возвращаются клиенту. returnRoute — относительный allowlisted путь. API-key создаётся через защищённый POST /v1/connections, описанный ниже, не через GET callback.
ConnectionSummary: id, tenantId, ownerId, providerId, pluginVersionId, authMode, placement, bindingId?, status:active/reauth_required/revoked/error, scopes, accountLabel, expiresAt?, capabilitiesRevision. bindingId для runner_private_store привязывает к одному разрешённому пулу/пользовательскому окружению. Verify не выполняет платный inference без отдельного opt-in; model list не гарантирует entitlement.
POST auth flow и verify принимают Idempotency-Key. OAuth callback одноразовый, проверяет state, expiry, actor binding и exact redirect URI; при потере пользовательской сессии завершение требует reauth владельца. Device polling выполняет broker по interval, не каждая открытая вкладка. Authentication errors возвращаются нормализованно без raw provider token response.
## Инференс как внутренний контракт
ProviderInvocation: invocationId, runId, attemptId, connectionId, modelId, request, deadlineAt, capabilityRequirements. Доступ проверяется по executionToken и активному lease. Обёртка DSH нормализует stream events, usageStatus и terminal outcome. Credentials доступны только разрешённому adapter/официальному клиенту; shared глобального аккаунта нет. Внешний provider request id сохраняется для диагностики и дедупликации, если API поддерживает её.
## Передача API key
POST /v1/connections принимает {providerId, methodId, placement, label, credentialInput?, bindingId?}; ответ 201 ConnectionSummary. Для server_vault credentialInput={apiKey:string} передаётся только в TLS body, исключается из access/error logs, tracing и сохранения idempotency payload; сервер хранит только хеш запроса и зашифрованный ключ. Для local_keychain/runner_private_store ключ принимает локальный broker, облачный API получает только подтверждённый bindingId. Переданный клиентом bindingId проверяется на владельца и proof-of-possession от доверенного broker. API не принимает локальный путь к чужому файлу или произвольный vault URI.
GET /v1/connections принимает cursor/limit и возвращает Page<ConnectionSummary>. ModelDescriptor: id, providerId, displayName, capabilities, contextWindow?, availability:unknown/available/unavailable, checkedAt?. Поля capabilities — набор поддержанных операций (text, tools, image_input, structured_output), а не гарантия доступа к модели по подписке.
AuthFlow создаётся только для oauth_pkce/device_code/official_client. Callback OAuth использует GET только для провайдеров с query response mode; иной response mode требует отдельного адаптера и conformance. По истечении expiresAt flow больше не может создавать connection. Внешний callback без пользовательской cookie проверяет flow binding, а выдача connection пользователю требует исходную сессию либо повторный вход владельца.
+13 -3
View File
@@ -13,14 +13,14 @@
| POST /v1/runs/{id}/cancel | reason? | 202 Run | Идемпотентная команда; terminal run → 409 |
| POST /v1/runs/{id}/retry | acknowledgedExternalEffects:boolean | 202 новый Run | parentRunId, новый ключ, проверка policy |
| POST /v1/runs/{id}/responses | responseId, inputRequestId, value | 202 Run | Только waiting_input; схема ответа и права |
| POST /v1/schedules | agentId, cron, timezone, overlapPolicy | 201 Schedule | operator; cron и timezone валидируются |
| PATCH /v1/schedules/{id} | cron?, timezone?, enabled?, overlapPolicy?; If-Match | 200 Schedule | 412 conflict |
| POST /v1/schedules | agentId, agentRevision, cron, timezone, overlapPolicy, misfirePolicy? | 201 Schedule | operator; cron и timezone валидируются |
| PATCH /v1/schedules/{id} | agentRevision?, cron?, timezone?, enabled?, overlapPolicy?, misfirePolicy?; If-Match | 200 Schedule | 412 conflict |
| GET /v1/schedules | cursor, limit | 200 Page<Schedule> | Tenant-scoped |
| DELETE /v1/schedules/{id} | If-Match | 204 | Останавливает будущие, не текущие Run |
Run: id, tenantId, agentId, agentRevisionId, status, specDigest, createdAt, startedAt?, finishedAt?, attemptCount, parentRunId?, error?, resultManifest?, inputRequest?. Полный spec доступен только субъектам с run:read-config; обычный status не содержит конфигурацию.
Schedule: id, tenantId, agentId, cron, timezone, overlapPolicy (skip/queue), enabled, nextRunAt, revision. Каждое срабатывание фиксирует текущую ревизию агента; уже созданный Run не меняется при обновлении агента. Cron интерпретируется как пять полей (минута, час, день месяца, месяц, день недели); шаг менее минуты не поддерживается.
Schedule: id, tenantId, agentId, agentRevisionId, cron, timezone, overlapPolicy (skip/queue), misfirePolicy (skip/coalesce), enabled, nextRunAt, revision. Расписание закрепляет agentRevisionId при создании. Новая ревизия применяется только явным PATCH расписания; уже созданные Run не меняются. Cron интерпретируется как пять полей (минута, час, день месяца, месяц, день недели); шаг менее минуты не поддерживается.
Limits: cpuMillis, memoryBytes, timeoutSeconds, outputBytes. Пользователь может запросить меньше предела policy; превышение даёт 422, а не скрытое повышение прав. Input — JSON по input schema template. PoolId не даёт доступ к чужому пулу.
@@ -31,3 +31,13 @@ Limits: cpuMillis, memoryBytes, timeoutSeconds, outputBytes. Пользоват
```
Отмена не гарантирует отмену уже совершённого внешнего действия. Reconcile технического статуса выполняет сервис исполнения; пользовательский запрос не переписывает состояние Runner произвольно.
## Семантика расписаний
GET /v1/schedules/{id} возвращает Schedule. POST /v1/schedules/preview принимает cron, timezone, from, count (1–10) и возвращает {occurrences:[UTC timestamp], warnings:[]}; сохранения не выполняет. Pause — PATCH enabled=false, resume — PATCH enabled=true; оба требуют If-Match. Возобновление не догоняет периоды явной паузы.
Cron: пять полей; день недели 0–6 (0 — воскресенье); если day-of-month и day-of-week ограничены одновременно, применяется OR. DST: несуществующее локальное время пропускается, двойное — первое вхождение. Обновление tzdata пересчитывает только будущие occurrences; версия tzdata записывается в аудит. Первый релиз не поддерживает jitter и интервалы меньше минуты.
Skip отбрасывает occurrence, если у расписания есть нетерминальный Run. Queue допускает один активный и максимум один ожидающий Run; лишние occurrences объединяются с ожидающим. При простое misfirePolicy=coalesce (default) выбирает последний пропущенный occurrence, skip не создаёт Run. Уникальность (scheduleId, scheduledAt) гарантируется БД. Проверка overlap и создание Run выполняются под блокировкой строки Schedule в одной транзакции. Ручные Run не учитываются в overlap, но учитываются в квотах tenant.
Отзыв доступа к агенту/connection, блокировка пакета и архивирование агента останавливают расписание с reason, не создавая бесконечную череду failed Run. Любой запуск повторно проверяет права владельца расписания. Локальное расписание требует работающего daemon; перенос в облако отключает прежнего владельца до включения нового.