Добавить дизайн Invest Harness и контракты платформы
This commit is contained in:
1 parent
5d31a39aae
commit
2af6dd78ba
21 files changed
+1322
-2
No files matched your search
@@ -0,0 +1,31 @@
|
||||
# API экземпляров агентов
|
||||
|
||||
Общие правила: [conventions.md](conventions.md). Все ресурсы scoped к tenant. Viewer читает разрешённые агенты; operator создаёт и настраивает свои, admin — разрешённые tenant-агенты.
|
||||
|
||||
| Метод и путь | Запрос | Ответ | Поведение |
|
||||
|---|---|---|---|
|
||||
| POST /v1/agents | name, templateVersionId, config, connections | 201 Agent | Создаёт revision=1 после проверки |
|
||||
| GET /v1/agents | cursor, limit | 200 Page<Agent> | Только доступные |
|
||||
| GET /v1/agents/{id} | — | 200 Agent с currentRevision | Проверка owner/tenant |
|
||||
| POST /v1/agents/{id}/config/validate | config, templateVersionId? | 200 ValidationResult | Без сохранения |
|
||||
| PUT /v1/agents/{id}/config | config, connections; If-Match | 200 AgentRevision | Полная конфигурация; 412 lost update |
|
||||
| GET /v1/agents/{id}/revisions/{revision} | — | 200 AgentRevision | Неизменяемый snapshot |
|
||||
| POST /v1/agents/{id}/upgrade-preview | targetTemplateVersionId | 200 UpgradePreview | Diff, requiredPermissions, ошибки |
|
||||
| POST /v1/agents/{id}/upgrade | targetTemplateVersionId, config, connections; If-Match | 200 AgentRevision | Новая ревизия, старые Run не меняются |
|
||||
| DELETE /v1/agents/{id} | If-Match | 202 operationId | Архивирует; активные Run требуют отдельной отмены |
|
||||
|
||||
Agent: id, tenantId, ownerId, name, currentRevision, archivedAt?, createdAt. AgentRevision: id, agentId, revision, templateVersionId, configSchemaDigest, config, connections, createdAt. Connections — map логического имени на connectionId, без секретов.
|
||||
|
||||
ValidationResult: valid:boolean, errors:[{path, code, message}], effectiveConfig. При семантически невалидной конфигурации endpoint validate возвращает 200 valid=false; endpoints сохранения возвращают 422. Ссылки на чужие подключения не раскрываются в errors.
|
||||
|
||||
UpgradePreview: fromVersionId, toVersionId, changes:[{path, kind}], validation, addedPermissions. Preview не разрешает новые доступы автоматически. Принятие upgrade заново проверяет rights и policy.
|
||||
|
||||
## Пример создания
|
||||
|
||||
```json
|
||||
{"name":"Обзор компаний","templateVersionId":"version_company_1","config":{"lookbackDays":90,"reportLanguage":"ru","includeSources":true},"connections":{}}
|
||||
```
|
||||
|
||||
## Подключения
|
||||
|
||||
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 после отзыва запрещено.
|
||||
@@ -0,0 +1,37 @@
|
||||
# API каталога
|
||||
|
||||
Общие правила: [conventions.md](conventions.md). Автор публикации — publisher соответствующего tenant. Читатель видит public или разрешённые tenant/private пакеты.
|
||||
|
||||
| Метод и путь | Запрос | Ответ | Доступ / особые ошибки |
|
||||
|---|---|---|---|
|
||||
| GET /v1/packages | kind, query, cursor, limit | 200 Page<Package> | Доступные пакеты |
|
||||
| GET /v1/packages/{id} | — | 200 Package | 404 для скрытого |
|
||||
| POST /v1/packages | kind, name, description, visibility | 201 Package | Publisher; 409 имя занято |
|
||||
| PATCH /v1/packages/{id} | description?, visibility?; If-Match | 200 Package | Publisher; нельзя менять kind |
|
||||
| GET /v1/packages/{id}/versions | cursor, limit | 200 Page<PackageVersion> | Читатель |
|
||||
| GET /v1/packages/{id}/versions/{version} | — | 200 PackageVersion | Читатель |
|
||||
| POST /v1/packages/{id}/versions | version, manifest | 201 VersionDraft | Publisher; Idempotency-Key |
|
||||
| POST /v1/packages/{id}/versions/{version}/upload | mediaType, sizeBytes, digest | 201 UploadGrant | Publisher; ограниченный путь |
|
||||
| POST /v1/packages/{id}/versions/{version}/publish | artifactRef, digest | 200 PackageVersion | Publisher; 409 другая версия; 422 несовместимость |
|
||||
| POST /v1/packages/{id}/versions/{version}/policy | status, reason; If-Match | 200 VersionPolicy | Admin; status deprecated/blocked/published |
|
||||
| POST /v1/resolutions | templateVersionId, environment | 200 Resolution | Читатель всех зависимостей; 422 conflict |
|
||||
| GET /v1/package-versions/{id}/config | — | 200 ConfigBundle | Закреплённые schema, defaults, uiSchema и digest |
|
||||
| POST /v1/package-versions/{id}/download | artifactDigest | 200 DownloadGrant | Повторная проверка policy и доступа |
|
||||
|
||||
## Формы
|
||||
|
||||
Package: id, tenantId, kind (harness/plugin/skill/template), name, description, visibility (public/tenant/private), ownerId, createdAt, revision.
|
||||
|
||||
VersionDraft: id, packageId, version, manifest, status=draft. PackageVersion: те же идентификаторы, artifactRef, artifactDigest, schemaDigest?, publishedAt. VersionPolicy хранится отдельно: status, reason?, revision. Поле version не разрешает перезапись содержимого.
|
||||
|
||||
Resolution: resolutionId, templateVersionId, packages:[{packageVersionId, kind, digest, artifactRef}], configSchemaDigest, requiredCapabilities, requiredPermissions. environment: os, architecture, protocolVersion, availableCapabilities. Resolution не содержит credentials и не обещает бессрочное право скачать заблокированный пакет.
|
||||
|
||||
ConfigBundle: schema (JSON Schema), defaults (object), uiSchema (object|null), schemaDigest. UI schema необязательна. OCI upload использует стандартный registry API с ограниченным токеном; upload ручка для него выдаёт registry reference и scope, а не S3 URL.
|
||||
|
||||
## Пример resolve
|
||||
|
||||
```json
|
||||
{"templateVersionId":"version_company_1","environment":{"os":"linux","architecture":"amd64","protocolVersion":"1","availableCapabilities":["oci-v1","cancel-v1"]}}
|
||||
```
|
||||
|
||||
Публикация возвращает успех только после проверки артефакта. Если загрузка завершилась, а запрос publish потерялся, повтор с тем же ключом безопасен. Удаление пакета, на который ссылаются сохранённые запуски, не входит в MVP; вместо него используются deprecated/blocked.
|
||||
@@ -0,0 +1,33 @@
|
||||
# Общие правила API v1
|
||||
|
||||
Это проектируемые публичные контракты Invest Harness. Базовый путь /v1; JSON UTF-8; HTTPS обязателен. Внутренние API движка не являются публичным контрактом.
|
||||
|
||||
## Типы и ошибки
|
||||
|
||||
Id — непрозрачная строка 1–128 символов; Timestamp — RFC3339 UTC; Digest — sha256: и 64 hex-символа; Revision — положительное целое. Денежные суммы задаются integer minor units + currency; CPU в millicores, память и файлы в bytes, интервалы в seconds. Дата расписания содержит отдельно IANA timezone.
|
||||
|
||||
Ошибка: {"error":{"code":"CONFIG_INVALID","message":"Настройки не соответствуют схеме","details":[{"path":"/lookbackDays","code":"maximum"}],"requestId":"req_example"}}. Никаких секретов или stack trace в ответе.
|
||||
|
||||
| HTTP | Значение |
|
||||
|---|---|
|
||||
| 400 | Невалидный синтаксис запроса |
|
||||
| 401 | Нет действительной идентичности |
|
||||
| 403 | Нет прав на известную разрешённую к раскрытию операцию |
|
||||
| 404 | Ресурс отсутствует или скрыт от вызывающего |
|
||||
| 409 | Конфликт состояния, digest, lease или idempotency payload |
|
||||
| 412 | Устаревший If-Match |
|
||||
| 422 | Валидный JSON нарушает контракт, зависимости или policy |
|
||||
| 429 | Квота / rate limit, Retry-After |
|
||||
| 503 | Временная недоступность зависимости |
|
||||
|
||||
## Доступ и повторные запросы
|
||||
|
||||
Сессия пользователя или сервисный bearer token. Membership и ресурсный tenant проверяются на каждой операции. Публичное чтение каталога не даёт доступ к приватным зависимостям. Cookie-мутации требуют CSRF-защиты. Запросы публикации, запуска, retry и resume требуют Idempotency-Key; область ключа tenant + principal + method + path. Hash тела связывается с ключом; другое тело → 409. Результат хранится минимум 7 дней, а уникальность attemptId и schedule occurrence действует независимо от TTL ключа.
|
||||
|
||||
PATCH/PUT изменяемых ресурсов требуют If-Match, ответ содержит ETag. Списки: limit 1..100, непрозрачный cursor, ответ {items:[], nextCursor:null}. Cursor фиксирует сортировку createdAt,id и фильтры, не заменяет авторизацию. DELETE с асинхронной очисткой возвращает 202 и operationId.
|
||||
|
||||
## События и файлы
|
||||
|
||||
SSE endpoint принимает Last-Event-ID; event id монотонен в пределах Run. После retention слишком старый cursor возвращает 410 с указанием получить snapshot. Повторные события дедуплицируются по eventId. SSE не является источником истины о состоянии: GET Run возвращает канонический snapshot.
|
||||
|
||||
Binary upload выполняется по scoped grant. Он содержит uploadId, url, expiresAt, maxBytes, requiredHeaders; постоянные записи хранят artifactId, не подписанный URL. Grant не считается подтверждением загрузки: finalize проверяет размер, digest и объект. По умолчанию ошибки не повторяются автоматически, кроме явных transient ошибок с ограниченным backoff и jitter.
|
||||
@@ -0,0 +1,37 @@
|
||||
# Runner Protocol v1
|
||||
|
||||
Новый контракт Invest Harness для заменяемых исполнителей. Стандартный движок подключается адаптером; он не обязан понимать этот API напрямую. ExecutionSpec одинаков для удалённой и локальной реализации.
|
||||
|
||||
## Регистрация
|
||||
|
||||
Admin создаёт POST /v1/runner-pools/{poolId}/registration-grants с {expiresInSeconds:600}; ответ 201 {grant, expiresAt}. Grant одноразовый, не журналируется. Runner вызывает POST /v1/runners/register с {grant, name, protocolVersion:"1", capabilities}; ответ 201 {runnerId, poolId, credential, expiresAt}. Долгоживущая идентичность хранится в защищённом хранилище исполнителя; credential обновляется через POST /v1/runners/{id}/credentials/rotate с текущей идентичностью и отзывается через DELETE /v1/runners/{id} администратором.
|
||||
|
||||
Capabilities: os, architecture, executionKinds:["oci"], features:["cancel-v1"], maxConcurrency. checkpoint-v1 и input-v1 опциональны. Профиль пула задаёт доверенную изоляцию и квоты отдельно от заявления capabilities.
|
||||
|
||||
## Выполнение
|
||||
|
||||
| Метод и путь | Запрос | Ответ |
|
||||
|---|---|---|
|
||||
| POST /v1/runner-jobs/claim | runnerId, availableSlots, capabilitiesRevision | 200 Assignment или 204 |
|
||||
| POST /v1/attempts/{id}/heartbeat | leaseEpoch, sequence, progress? | 200 {leaseUntil, 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 пула.
|
||||
|
||||
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.
|
||||
|
||||
Suspend допустим только для checkpoint-v1 + input-v1. InputRequest: id, schema, prompt, expiresAt. Платформа принимает checkpoint, переводит Run в waiting_input, отзывает lease; новый Attempt получает checkpointRef и проверенный ответ. Без checkpoint протокол не обещает сохранение памяти процесса.
|
||||
|
||||
## Локальный процесс
|
||||
|
||||
Адаптер materialize-ит immutable ExecutionSpec в /input/execution.json, входные файлы — read-only /input/files; вывод — /output. Разрешённые secrets передаются отдельным каналом и не сериализуются в spec. Entrypoint — массив аргументов, не shell-строка. События JSONL поступают через отдельный pipe или файл адаптера, stdout/stderr считаются логами. Адаптер читает /output/result.json и загружает разрешённые артефакты; ссылки вне /output и symlink traversal запрещены.
|
||||
|
||||
Соглашение путей относится к стандартному OCI-адаптеру. Другой Runner может реализовать иной транспорт при сохранении семантики ExecutionSpec и результатов. Локальный offline Runner не вызывает облачные claim/heartbeat, но ведёт эквивалентное локальное состояние.
|
||||
|
||||
## Минимальная проверка совместимости
|
||||
|
||||
Runner обязан показать: корректный success; failure; отказ при несовместимом spec; отмену; тайм-аут; повтор complete без дублей; отказ с просроченным lease; изоляцию файлов между задачами; корректный upload digest; отсутствие secrets в событиях. Resume проверяется только при соответствующей capability. Managed и customer-managed пулы отображаются раздельно.
|
||||
@@ -0,0 +1,33 @@
|
||||
# API запусков и расписаний
|
||||
|
||||
Общие правила: [conventions.md](conventions.md). Operator запускает и отменяет доступные ему агенты; viewer читает разрешённые Run. Доступ к самому агенту не заменяет проверку прав на его connections.
|
||||
|
||||
| Метод и путь | Запрос | Ответ | Особенности |
|
||||
|---|---|---|---|
|
||||
| POST /v1/runs | agentId, agentRevision, input, poolId?, limits? | 202 Run | Idempotency-Key, policy и квоты |
|
||||
| GET /v1/runs | agentId?, status?, cursor, limit | 200 Page<Run> | Tenant-scoped |
|
||||
| GET /v1/runs/{id} | — | 200 Run | Каноническое состояние |
|
||||
| GET /v1/runs/{id}/events | Last-Event-ID | SSE RunEvent | 410 при истёкшем cursor |
|
||||
| GET /v1/runs/{id}/artifacts | cursor, limit | 200 Page<Artifact> | Без долгоживущих URL |
|
||||
| POST /v1/runs/{id}/artifacts/{artifactId}/download | — | 200 DownloadGrant | Повторная проверка прав |
|
||||
| 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 |
|
||||
| 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 интерпретируется как пять полей (минута, час, день месяца, месяц, день недели); шаг менее минуты не поддерживается.
|
||||
|
||||
Limits: cpuMillis, memoryBytes, timeoutSeconds, outputBytes. Пользователь может запросить меньше предела policy; превышение даёт 422, а не скрытое повышение прав. Input — JSON по input schema template. PoolId не даёт доступ к чужому пулу.
|
||||
|
||||
## Пример
|
||||
|
||||
```json
|
||||
{"agentId":"agent_company","agentRevision":1,"input":{"company":"Example Company"},"limits":{"cpuMillis":1000,"memoryBytes":1073741824,"timeoutSeconds":900,"outputBytes":104857600}}
|
||||
```
|
||||
|
||||
Отмена не гарантирует отмену уже совершённого внешнего действия. Reconcile технического статуса выполняет сервис исполнения; пользовательский запрос не переписывает состояние Runner произвольно.
|
||||
Reference in new issue
Block a user