Добавить дизайн Invest Harness и контракты платформы
This commit is contained in:
1 parent
5d31a39aae
commit
2af6dd78ba
21 files changed
+1322
-2
No files matched your search
@@ -1,3 +1,5 @@
|
||||
# invest-harness
|
||||
# Invest Harness
|
||||
|
||||
Invest Harness — платформа инвестиционных ИИ-агентов. Архитектура и контракты системы.
|
||||
Платформа инвестиционных ИИ-агентов: каталог harness’ов, плагинов, скиллов и шаблонов, настройка через формы и запуск локально или удалённо.
|
||||
|
||||
Проект на стадии проектирования. [Дизайн системы](designDocument/design.md).
|
||||
@@ -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 произвольно.
|
||||
@@ -0,0 +1,29 @@
|
||||
# Каталог пакетов
|
||||
|
||||
Каталог — отдельный Go-сервис со своей PostgreSQL БД. Он владеет метаданными harness, plugin, skill и template. OCI-образы обслуживает registry, архивы — S3. Прямой доступ других сервисов к таблицам каталога запрещён; используется API.
|
||||
|
||||
## Пакет и версия
|
||||
|
||||
Package содержит id, tenantId, kind, name, description, visibility и ownerId. Имя уникально внутри tenant и kind. PackageVersion содержит version, manifest, artifactDigest, artifactRef, schemaDigest, createdAt и publisherId. Содержимое версии неизменяемо. Отдельная запись VersionPolicy хранит status, reason и changedAt: блокировка не переписывает исходный пакет.
|
||||
|
||||
Harness описывает поддерживаемый runtime, entrypoint и протокол ввода/вывода. Plugin описывает расширение и совместимость с harness. Skill содержит инструкции, ресурсы и при необходимости скрипты. Template объединяет точные или ограниченные версии зависимостей, инструкции и схему конфигурации. Skill с исполняемым кодом проходит те же проверки, что plugin.
|
||||
|
||||
## Публикация
|
||||
|
||||
Автор с ролью publisher создаёт draft, получает ограниченный upload grant и загружает артефакт. Для OCI он использует стандартный registry push. Publish проверяет размер, digest, формат манифеста, ссылки внутри архива, схему конфигурации, доступ к зависимостям и отсутствие циклов. Архив не может содержать выходящие за корень пути, абсолютные пути или небезопасные symlink.
|
||||
|
||||
Только после проверки артефакта транзакция публикует версию. Повтор Publish с тем же Idempotency-Key возвращает прежний результат. То же имя версии с другим digest даёт 409. Объекты без опубликованной версии очищаются после 24 часов — исходное значение, настраиваемое администратором.
|
||||
|
||||
## Resolve
|
||||
|
||||
Resolver принимает templateVersionId и профиль среды. Он строит ацикличный граф зависимостей, проверяет совместимость, выбирает конкретные версии и возвращает lock со всеми digest. Диапазоны версий разрешены на входе публикации шаблона, но не в ExecutionSpec. Конфликт версий возвращается с цепочками зависимостей; молчаливый выбор несовместимого пакета запрещён.
|
||||
|
||||
Доступ проверяется при resolve, выдаче download grant и назначении Attempt. Публичный шаблон не должен требовать недоступную аудитории приватную зависимость. Блокировка версии запрещает новые назначения; для уже работающих Attempt администратор выбирает policy cancel или allow_finish с аудитом.
|
||||
|
||||
## Обновления и хранение
|
||||
|
||||
Обновление шаблона не изменяет существующие AgentRevision. Пользователь получает preview изменений и ошибок валидации. Новая ревизия создаётся только после успешной проверки. В MVP миграция ручная через форму; исполняемые миграции автора не запускаются в браузере.
|
||||
|
||||
Deprecated версия доступна закреплённым агентам, но скрыта из стандартного выбора. Удаление referenced-версии возвращает 409. Физическая очистка допускается после retention запусков, проверки ссылок и grace period. Immutable артефакты адресуются по digest; signed URL не сохраняется в постоянном lock.
|
||||
|
||||
API: [catalog-api.md](api/catalog-api.md). Все диаграммы: [design.md](design.md).
|
||||
@@ -0,0 +1,38 @@
|
||||
# Пользовательская конфигурация
|
||||
|
||||
## Контракт автора
|
||||
|
||||
Шаблон содержит config.schema.json, config.defaults.json и необязательную ui.schema.json. JSON Schema описывает данные, UI Schema — только представление. Собственные параметры harness и plugins располагаются в пространствах имён harness и plugins.<packageId>; схема template объединяет их без конфликтов имён.
|
||||
|
||||
Базовый диалект — JSON Schema Draft 2020-12. MVP renderer поддерживает object, string, number, integer, boolean, array, enum, required, additionalProperties, minimum/maximum, minLength/maxLength, minItems/maxItems и локальные $ref/$defs. oneOf и условные секции включаются только после проверки renderer и валидатора. Неизвестные UI-компоненты и неподдерживаемые keywords отвергаются при публикации, а не игнорируются. Удалённые $ref запрещены. Ограничиваются размер, вложенность и сложность схемы. На старте запрещены пользовательские regex pattern, пока не реализованы лимиты их выполнения.
|
||||
|
||||
## Применение значений
|
||||
|
||||
1. Загрузить схему и defaults, закреплённые по digest.
|
||||
2. Создать конфигурацию из defaults и явных значений пользователя: objects объединяются по ключам, arrays заменяются целиком; null допустим только по схеме и не означает удаление.
|
||||
3. Проверить итоговую конфигурацию одним согласованным набором правил.
|
||||
4. Проверить права на подключения и ограничения организации.
|
||||
5. Сохранить полный результат как новую AgentRevision с schemaDigest.
|
||||
|
||||
JSON Schema default — аннотация, а не команда валидатору. defaults-файл при публикации обязан соответствовать схеме после объединения с обязательными вводимыми полями; отсутствующие обязательные поля пользователь заполняет до сохранения. Сервер не доверяет клиентской валидации.
|
||||
|
||||
## UI
|
||||
|
||||
Web UI: TypeScript, React, JSON Forms; серверный валидатор — совместимая Go-библиотека Draft 2020-12, точная реализация выбирается по общему корпусу контрактных примеров. UI schema использует только встроенные контролы и layout, без JavaScript, HTML и внешних скриптов. Описания отображаются как безопасный текст.
|
||||
|
||||
Поля для подключения к сервису отображают selector ConnectionRef. Секретные значения не входят в JSON конфигурации, defaults, пример или URL. readOnly, writeOnly и скрытие поля в UI не заменяют server-side permission checks. Пользователь не может через config повысить лимит ресурсов, изменить tenantId или разрешить новые сетевые доступы.
|
||||
|
||||
## Версии и изменения
|
||||
|
||||
PUT конфигурации требует If-Match. При конфликте ревизии сервер возвращает 412. UI показывает различия, пользователь повторно сохраняет результат. Запуск фиксирует agentRevisionId, schemaDigest и effectiveConfig; последующие изменения агента не влияют на Run.
|
||||
|
||||
Новая версия шаблона сначала проверяется на копии конфигурации. При несовместимости показываются JSON Pointer путей и сообщения ошибок. Старая ревизия остаётся рабочей до явного обновления. В MVP автоматических исполняемых миграций нет.
|
||||
|
||||
## Источники и пример
|
||||
|
||||
- [JSON Schema Draft 2020-12](https://json-schema.org/draft/2020-12)
|
||||
- [Аннотации и default](https://json-schema.org/understanding-json-schema/reference/annotations)
|
||||
- [JSON Forms UI Schema](https://jsonforms.io/docs/uischema/)
|
||||
- [Пример схемы](contracts/examples/config.schema.json)
|
||||
- [Начальные значения](contracts/examples/config.defaults.json)
|
||||
- [Представление формы](contracts/examples/ui.schema.json)
|
||||
@@ -0,0 +1,44 @@
|
||||
# Модель данных
|
||||
|
||||
Типы в этой таблице определяют семантику API. JSON Schema ниже задают машиночитаемую основу контракта; дополнительные межресурсные проверки выполняются сервисами.
|
||||
|
||||
| Сущность | Ключевые поля | Владелец |
|
||||
|---|---|---|
|
||||
| Tenant | id, name, policyRevision | Platform API |
|
||||
| Membership | tenantId, userId, roles | Identity / Platform API |
|
||||
| Package | id, tenantId, kind, name, visibility, ownerId | Catalog |
|
||||
| PackageVersion | id, packageId, version, manifest, artifactDigest | Catalog |
|
||||
| VersionPolicy | versionId, status, reason, revision | Catalog |
|
||||
| Agent | id, tenantId, ownerId, name, currentRevision | Platform API |
|
||||
| AgentRevision | id, agentId, revision, templateVersionId, config, connections, schemaDigest | Platform API |
|
||||
| Connection | id, tenantId, provider, scopes, encryptedSecretRef | Secrets / Platform API |
|
||||
| Run | id, tenantId, agentRevisionId, status, specDigest, parentRunId | Platform API |
|
||||
| Attempt | id, runId, runnerId, leaseEpoch, leaseUntil, status | Dispatcher |
|
||||
| RunnerPool | id, tenantId, trustPolicy, quotas | Dispatcher |
|
||||
| Runner | id, poolId, capabilities, credentialStatus, lastSeenAt | Dispatcher |
|
||||
| Artifact | id, runId, attemptId, objectKey, digest, sizeBytes, mediaType | Platform API |
|
||||
| Schedule | id, agentId, cron, timezone, nextRunAt, overlapPolicy | Dispatcher |
|
||||
| Outbox | id, aggregateId, eventType, payload, deliveredAt | Пишущий сервис |
|
||||
|
||||
## Инварианты
|
||||
|
||||
- Между БД нет внешних ключей: идентификаторы проверяются API. Каждый сервис владеет своими транзакциями и outbox.
|
||||
- Run ссылается на immutable AgentRevision и ExecutionSpec; повторная проверка прав не изменяет их содержимое.
|
||||
- Published PackageVersion не перезаписывается. Политика блокировки хранится отдельно.
|
||||
- Попытка имеет только одного текущего владельца lease; epoch монотонно растёт при новом назначении.
|
||||
- Terminal report и смена состояния сохраняются одной транзакцией вместе с событием outbox.
|
||||
- Resource ID не является доказательством доступа. Tenant scope проверяется независимо.
|
||||
- ConnectionRef никогда не содержит значение секрета.
|
||||
- Artifact считается готовым после проверки объекта и digest, а не после выдачи upload URL.
|
||||
|
||||
## Схемы
|
||||
|
||||
- [Манифест пакета](package-manifest.schema.json) — общий envelope четырёх видов пакетов.
|
||||
- [ExecutionSpec](execution-spec.schema.json) — конкретный состав запуска.
|
||||
- [Событие](run-event.schema.json) — события исполнителя.
|
||||
- [Пример manifest](examples/template-manifest.json).
|
||||
- [Пример ExecutionSpec](examples/execution-spec.json).
|
||||
|
||||
Id — opaque string. Version — SemVer строка; разрешение диапазонов выполняет resolver до фиксации ExecutionSpec. Digest — SHA-256. Timestamps — RFC3339 UTC. UUID не навязывается публичным контрактом.
|
||||
|
||||
Package manifest с kind=template обязан указать harnessVersionId, configSchemaRef и зависимости. Harness обязан указать entrypoint и executionKind. Plugin и skill указывают совместимость supportedHarnesses. Отсутствующая optional capability означает, что возможность недоступна; поле не может быть выведено из названия пакета.
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"lookbackDays": 90,
|
||||
"reportLanguage": "ru",
|
||||
"includeSources": true
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"type": "object",
|
||||
"title": "Настройки обзора компаний",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"lookbackDays",
|
||||
"reportLanguage",
|
||||
"includeSources"
|
||||
],
|
||||
"properties": {
|
||||
"lookbackDays": {
|
||||
"type": "integer",
|
||||
"title": "Период анализа в днях",
|
||||
"minimum": 1,
|
||||
"maximum": 365,
|
||||
"default": 90
|
||||
},
|
||||
"reportLanguage": {
|
||||
"type": "string",
|
||||
"title": "Язык отчёта",
|
||||
"enum": [
|
||||
"ru",
|
||||
"en"
|
||||
],
|
||||
"default": "ru"
|
||||
},
|
||||
"includeSources": {
|
||||
"type": "boolean",
|
||||
"title": "Показывать источники",
|
||||
"default": true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"protocolVersion": "1",
|
||||
"runId": "run_example",
|
||||
"agentRevisionId": "revision_example_1",
|
||||
"templateVersionId": "template_example_1",
|
||||
"packages": [
|
||||
{
|
||||
"packageVersionId": "template_example_1",
|
||||
"kind": "template",
|
||||
"digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
"artifactRef": "artifact_template_example"
|
||||
},
|
||||
{
|
||||
"packageVersionId": "harness_example_1",
|
||||
"kind": "harness",
|
||||
"digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
|
||||
"artifactRef": "registry.example.invalid/invest/runtime@sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
|
||||
}
|
||||
],
|
||||
"configSchemaDigest": "sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc",
|
||||
"config": {
|
||||
"lookbackDays": 90,
|
||||
"reportLanguage": "ru",
|
||||
"includeSources": true
|
||||
},
|
||||
"input": {
|
||||
"company": "Example Company"
|
||||
},
|
||||
"connections": {},
|
||||
"limits": {
|
||||
"cpuMillis": 1000,
|
||||
"memoryBytes": 1073741824,
|
||||
"timeoutSeconds": 900,
|
||||
"outputBytes": 104857600
|
||||
},
|
||||
"requiredCapabilities": [
|
||||
"oci-v1",
|
||||
"cancel-v1"
|
||||
],
|
||||
"entrypoint": [
|
||||
"/app/entrypoint",
|
||||
"/input/execution.json"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"manifestVersion": "1",
|
||||
"kind": "template",
|
||||
"name": "company-review",
|
||||
"version": "1.0.0",
|
||||
"artifact": {
|
||||
"ref": "artifact_template_example",
|
||||
"digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
"mediaType": "application/gzip"
|
||||
},
|
||||
"dependencies": [
|
||||
{
|
||||
"packageVersionId": "harness_example_1",
|
||||
"digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
|
||||
}
|
||||
],
|
||||
"harnessVersionId": "harness_example_1",
|
||||
"configSchemaRef": "config.schema.json",
|
||||
"defaultsRef": "config.defaults.json",
|
||||
"uiSchemaRef": "ui.schema.json",
|
||||
"requiredCapabilities": [
|
||||
"oci-v1",
|
||||
"cancel-v1"
|
||||
],
|
||||
"requiredPermissions": [],
|
||||
"retrySafe": false
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"type": "VerticalLayout",
|
||||
"elements": [
|
||||
{
|
||||
"type": "Control",
|
||||
"scope": "#/properties/lookbackDays"
|
||||
},
|
||||
{
|
||||
"type": "Control",
|
||||
"scope": "#/properties/reportLanguage"
|
||||
},
|
||||
{
|
||||
"type": "Control",
|
||||
"scope": "#/properties/includeSources"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"title": "ExecutionSpec v1",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"protocolVersion",
|
||||
"runId",
|
||||
"agentRevisionId",
|
||||
"templateVersionId",
|
||||
"packages",
|
||||
"configSchemaDigest",
|
||||
"config",
|
||||
"input",
|
||||
"connections",
|
||||
"limits",
|
||||
"requiredCapabilities",
|
||||
"entrypoint"
|
||||
],
|
||||
"properties": {
|
||||
"protocolVersion": {
|
||||
"const": "1"
|
||||
},
|
||||
"runId": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 128
|
||||
},
|
||||
"agentRevisionId": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 128
|
||||
},
|
||||
"templateVersionId": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 128
|
||||
},
|
||||
"packages": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"packageVersionId",
|
||||
"kind",
|
||||
"digest",
|
||||
"artifactRef"
|
||||
],
|
||||
"properties": {
|
||||
"packageVersionId": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 128
|
||||
},
|
||||
"kind": {
|
||||
"enum": [
|
||||
"harness",
|
||||
"plugin",
|
||||
"skill",
|
||||
"template"
|
||||
]
|
||||
},
|
||||
"digest": {
|
||||
"type": "string",
|
||||
"pattern": "^sha256:[a-f0-9]{64}$"
|
||||
},
|
||||
"artifactRef": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"configSchemaDigest": {
|
||||
"type": "string",
|
||||
"pattern": "^sha256:[a-f0-9]{64}$"
|
||||
},
|
||||
"config": {
|
||||
"type": "object"
|
||||
},
|
||||
"input": {},
|
||||
"connections": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 128
|
||||
}
|
||||
},
|
||||
"limits": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"cpuMillis",
|
||||
"memoryBytes",
|
||||
"timeoutSeconds",
|
||||
"outputBytes"
|
||||
],
|
||||
"properties": {
|
||||
"cpuMillis": {
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"memoryBytes": {
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"timeoutSeconds": {
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"outputBytes": {
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"requiredCapabilities": {
|
||||
"type": "array",
|
||||
"uniqueItems": true,
|
||||
"items": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
}
|
||||
},
|
||||
"entrypoint": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
}
|
||||
},
|
||||
"checkpointRef": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 128
|
||||
},
|
||||
"resumeInput": {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"title": "Invest Harness package manifest v1",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"manifestVersion",
|
||||
"kind",
|
||||
"name",
|
||||
"version",
|
||||
"artifact",
|
||||
"dependencies"
|
||||
],
|
||||
"properties": {
|
||||
"manifestVersion": {
|
||||
"const": "1"
|
||||
},
|
||||
"kind": {
|
||||
"enum": [
|
||||
"harness",
|
||||
"plugin",
|
||||
"skill",
|
||||
"template"
|
||||
]
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"version": {
|
||||
"type": "string",
|
||||
"pattern": "^\\d+\\.\\d+\\.\\d+(-[0-9A-Za-z.-]+)?(\\+[0-9A-Za-z.-]+)?$"
|
||||
},
|
||||
"artifact": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"ref",
|
||||
"digest",
|
||||
"mediaType"
|
||||
],
|
||||
"properties": {
|
||||
"ref": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"digest": {
|
||||
"type": "string",
|
||||
"pattern": "^sha256:[a-f0-9]{64}$"
|
||||
},
|
||||
"mediaType": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"packageVersionId",
|
||||
"digest"
|
||||
],
|
||||
"properties": {
|
||||
"packageVersionId": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 128
|
||||
},
|
||||
"digest": {
|
||||
"type": "string",
|
||||
"pattern": "^sha256:[a-f0-9]{64}$"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"harnessVersionId": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 128
|
||||
},
|
||||
"configSchemaRef": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"defaultsRef": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"uiSchemaRef": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"executionKind": {
|
||||
"enum": [
|
||||
"oci",
|
||||
"native"
|
||||
]
|
||||
},
|
||||
"entrypoint": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
}
|
||||
},
|
||||
"supportedHarnesses": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 128
|
||||
}
|
||||
},
|
||||
"requiredCapabilities": {
|
||||
"type": "array",
|
||||
"uniqueItems": true,
|
||||
"items": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
}
|
||||
},
|
||||
"requiredPermissions": {
|
||||
"type": "array",
|
||||
"uniqueItems": true,
|
||||
"items": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
}
|
||||
},
|
||||
"retrySafe": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
"kind": {
|
||||
"const": "template"
|
||||
}
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"required": [
|
||||
"harnessVersionId",
|
||||
"configSchemaRef"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
"kind": {
|
||||
"const": "harness"
|
||||
}
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"required": [
|
||||
"executionKind",
|
||||
"entrypoint"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
"kind": {
|
||||
"enum": [
|
||||
"plugin",
|
||||
"skill"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"required": [
|
||||
"supportedHarnesses"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"title": "RunEvent v1",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"eventId",
|
||||
"attemptId",
|
||||
"sequence",
|
||||
"occurredAt",
|
||||
"type",
|
||||
"payload"
|
||||
],
|
||||
"properties": {
|
||||
"eventId": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 128
|
||||
},
|
||||
"attemptId": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 128
|
||||
},
|
||||
"sequence": {
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"occurredAt": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"progress",
|
||||
"log",
|
||||
"artifact_ready",
|
||||
"input_requested",
|
||||
"diagnostic"
|
||||
]
|
||||
},
|
||||
"payload": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,408 @@
|
||||
# Invest Harness
|
||||
|
||||
## Определения
|
||||
|
||||
| Термин | Значение |
|
||||
|---|---|
|
||||
| Harness | Движок и обвязка агента: цикл выполнения, управление контекстом, состоянием и вызовами инструментов |
|
||||
| Runtime | Среда выполнения программы: например, Node.js или Python; определяется пакетом harness’а |
|
||||
| Plugin | Программное расширение, добавляющее инструменты, интеграции или возможности |
|
||||
| Skill | Инструкции и вспомогательные материалы для конкретной задачи; может содержать скрипты, но сам по себе не предоставляет доступы |
|
||||
| Шаблон агента | Инструкции, совместимый harness, плагины, скиллы и схема пользовательских настроек |
|
||||
| Экземпляр агента | Шаблон с настройками и подключениями пользователя |
|
||||
| Run | Логический запуск с неизменяемым снимком версий, входных данных и настроек |
|
||||
| Attempt | Одна попытка исполнения Run, назначенная конкретному Runner |
|
||||
| Runner | Заменяемый исполнитель, подготавливающий окружение и запускающий harness |
|
||||
| Каталог | Сервис метаданных пакетов, версий, совместимости и прав доступа с собственной БД |
|
||||
| Registry | API доставки OCI-образов; байты образов находятся в отдельном хранилище |
|
||||
| ExecutionSpec | Неизменяемое описание запуска, общее для локального и удалённого исполнения |
|
||||
| Capability | Проверяемая возможность исполнителя: среда, протокол, отмена, checkpoint |
|
||||
| Tenant | Организация или персональное пространство с отдельной границей доступа |
|
||||
| Lease | Ограниченное по времени назначение Attempt исполнителю |
|
||||
|
||||
## Концепция и статус
|
||||
|
||||
Invest Harness — платформа агентов для инвестиций. Автор публикует шаблон и его зависимости, пользователь выбирает шаблон, настраивает его через автоматически построенную форму и запускает локально или удалённо. Оба режима используют один формат пакетов и ExecutionSpec.
|
||||
|
||||
Документ задаёт целевую архитектуру и контракты первой версии. Реализация сервисов пока не входит в этот репозиторий. Выбранные технологии — проектные решения; численные показатели масштабирования — гипотезы до измерений.
|
||||
|
||||
Для платформы содержимое окружения Runner — чёрный ящик. Внутри могут работать агентские цепочки, произвольные программы, разные модели и интеграции. Платформа отвечает за доставку пакетов, доступы, ресурсы, состояния и результаты. Она не задаёт инвестиционную стратегию и не гарантирует корректность финансовых выводов или внешних действий. Способность продолжить работу после сбоя должна быть явно объявлена пакетом.
|
||||
|
||||
Локальный запуск уже загруженного публичного пакета не требует облачного аккаунта. Сеть может требоваться модели или инструментам. Публичность описания пакета не означает доступ к пользовательским данным.
|
||||
|
||||
## Пользовательские сценарии
|
||||
|
||||
1. Автор загружает версию пакета и схемы, проверяет зависимости и публикует версию.
|
||||
2. Пользователь выбирает шаблон, заполняет форму и подключает разрешённые интеграции. Сохраняется ревизия экземпляра агента.
|
||||
3. Пользователь запускает агента локально, в управляемом пуле или на своём Runner.
|
||||
4. Платформа показывает состояние, события, файлы и итог, позволяет отменить запуск.
|
||||
5. Если harness поддерживает запрос ввода и checkpoint, пользователь может ответить и продолжить выполнение отдельной попыткой.
|
||||
6. Обновление шаблона создаёт новую ревизию агента после проверки настроек; текущие запуски не меняются.
|
||||
|
||||
## Архитектура C4
|
||||
|
||||
Контейнер в C4 — отдельно запускаемое приложение или хранилище, не обязательно Docker-контейнер. Диаграммы используют нотацию Mermaid с явными типами элементов C4. C3 раскрывает логические модули, а не отдельные микросервисы.
|
||||
|
||||
### C1 Контекст
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
U["Пользователь / Person"]
|
||||
A["Автор и администратор / Person"]
|
||||
P["Invest Harness / Software System"]
|
||||
M["Модели и внешние интеграции / External System"]
|
||||
L["Локальное окружение пользователя / External System"]
|
||||
U -->|Выбор, настройка, запуск| P
|
||||
A -->|Публикация и управление| P
|
||||
P -->|Разрешённые вызовы| M
|
||||
P -->|Пакеты и конфигурация| L
|
||||
L -->|Вызовы по локальным доступам| M
|
||||
```
|
||||
|
||||
### C2 Контейнеры и хранилища
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
U["Пользователь / Person"]
|
||||
subgraph P["Invest Harness / Software System"]
|
||||
G["Gateway / Container / Nginx"]
|
||||
W["Web UI / Container / React, TypeScript, JSON Forms"]
|
||||
I["Identity / Logical component of platform API / Sessions, OIDC"]
|
||||
B["Platform API / Container / Go, REST"]
|
||||
DB[("Platform DB / PostgreSQL / роли, агенты, запуски, аудит")]
|
||||
C["Каталог / Container / Go, REST"]
|
||||
CD[("Catalog DB / PostgreSQL / пакеты и версии")]
|
||||
PS[("Package Storage / S3 / архивы плагинов, скиллов, шаблонов")]
|
||||
D["Диспетчер / Container / Go"]
|
||||
E["Сервис исполнения / Container / Go / workflow, jobs, адаптер"]
|
||||
ED[("Execution DB / PostgreSQL / назначения и статусы движка")]
|
||||
R["Registry API / Component of execution service / Go, OCI"]
|
||||
RS[("Registry Storage / S3 / манифесты и слои образов")]
|
||||
ES[("Execution Storage / S3 / логи и технические артефакты")]
|
||||
X["Runner pool / Containers / стандартный адаптер или свой Runner"]
|
||||
J["Job / Ephemeral container / harness и зависимости"]
|
||||
T["Опциональный шлюз инструментов / Container / Go"]
|
||||
S["Secrets / Managed service / KMS-backed encryption"]
|
||||
F[("User Storage / S3 / вход, результат, checkpoint")]
|
||||
O["Наблюдаемость / OTel, Prometheus, Grafana, Loki"]
|
||||
end
|
||||
L["Local client / Container on user device / Go CLI, Docker"]
|
||||
M["LLM и внешние API / External Systems"]
|
||||
U --> G
|
||||
G --> W
|
||||
G --> B
|
||||
W -->|Вход| I
|
||||
I --> DB
|
||||
B -->|Проверка личности| I
|
||||
B --> DB
|
||||
B --> C
|
||||
C --> CD
|
||||
C --> PS
|
||||
D -->|Готовые задания| DB
|
||||
D -->|Зафиксировать версии| C
|
||||
D -->|Назначение через адаптер| E
|
||||
E --> ED
|
||||
E --> ES
|
||||
R -->|Метаданные| ED
|
||||
R -->|Байты образов| RS
|
||||
X -->|Runner Protocol, lease, heartbeat| D
|
||||
X -->|OCI pull через API| R
|
||||
X --> J
|
||||
J -->|Токен запуска| B
|
||||
J --> F
|
||||
J --> T
|
||||
J -->|При явно разрешённом прямом доступе| M
|
||||
T -->|Проверка прав и бюджета| B
|
||||
T --> S
|
||||
T --> M
|
||||
B --> F
|
||||
D --> S
|
||||
B -.-> O
|
||||
C -.-> O
|
||||
E -.-> O
|
||||
X -.-> O
|
||||
T -.-> O
|
||||
U --> L
|
||||
L -->|Пакеты| C
|
||||
L -->|Образы| R
|
||||
L -->|Локальные credentials| M
|
||||
```
|
||||
|
||||
Путь X → D обозначает публичный протокол собственной реализации Runner. Стандартный исполнитель соединяется со своим движком через адаптер внутри сервиса исполнения; он не обязан нативно поддерживать этот протокол. Диспетчер остаётся единственным владельцем бизнес-решения о назначении. Нельзя одновременно выдавать один Attempt через обе системы очередей.
|
||||
|
||||
Gateway логически обслуживает публичные HTTPS-маршруты; внутренние стрелки сокращены. Авторизация выполняется в принимающем сервисе и не заменяется наличием Gateway. Registry на схеме вынесен для наглядности, но может работать в одном процессе с сервисом исполнения. S3 buckets логически отдельны; физический сервис может быть общим.
|
||||
|
||||
### C3 Каталог
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
U["Platform API / Author"]
|
||||
subgraph C["Каталог / Container"]
|
||||
A["REST API / Go"]
|
||||
P["Проверка прав и видимости"]
|
||||
V["Проверка манифеста, схем и digest"]
|
||||
Q["Resolver зависимостей и совместимости"]
|
||||
N["Публикация неизменяемой версии"]
|
||||
end
|
||||
DB[("Catalog DB / PostgreSQL")]
|
||||
S[("Package Storage / S3")]
|
||||
R["Registry API / OCI"]
|
||||
U --> A
|
||||
A --> P
|
||||
P --> V
|
||||
P --> Q
|
||||
V --> N
|
||||
V --> S
|
||||
V --> R
|
||||
N --> DB
|
||||
Q --> DB
|
||||
```
|
||||
|
||||
### C3 Исполнение и registry
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
D["Диспетчер / Go"]
|
||||
X["Стандартный исполнитель или свой Runner"]
|
||||
OCI["OCI client / Docker Engine"]
|
||||
subgraph E["Сервис исполнения / Container"]
|
||||
A["Execution adapter API / Go"]
|
||||
P["Сервисная авторизация"]
|
||||
W["Workflow и jobs"]
|
||||
L["Назначения и состояния движка"]
|
||||
H["Приём событий, логов и завершения"]
|
||||
R["Registry API / OCI push и pull"]
|
||||
end
|
||||
DB[("Execution DB / PostgreSQL")]
|
||||
RS[("Registry Storage / S3 / image bytes")]
|
||||
ES[("Execution Storage / S3 / logs and artifacts")]
|
||||
D --> A
|
||||
A --> P
|
||||
P --> W
|
||||
W --> L
|
||||
X -->|Через соответствующий адаптер| L
|
||||
X --> H
|
||||
H --> L
|
||||
L --> DB
|
||||
H --> ES
|
||||
OCI --> R
|
||||
R --> P
|
||||
R -->|Метаданные| DB
|
||||
R -->|Манифесты и слои| RS
|
||||
```
|
||||
|
||||
### Технологии и границы реализации
|
||||
|
||||
| Система | Стек | Как создаётся |
|
||||
|---|---|---|
|
||||
| Web UI | TypeScript, React, JSON Forms | Новый интерфейс |
|
||||
| Platform API и identity | Go, REST, OpenAPI, сессии, OIDC | Продуктовый API с адаптацией существующих механизмов аккаунтов |
|
||||
| Каталог | Go, PostgreSQL, S3 API | Новый отдельный сервис |
|
||||
| Диспетчер | Go, PostgreSQL, transactional outbox | Новый слой квот и назначений |
|
||||
| Сервис исполнения | Go, SQL, существующие workflow/jobs | Адаптация готового ядра; внутренние детали скрыты контрактом |
|
||||
| Registry | Go, OCI Distribution API, PostgreSQL, S3 | Переиспользуемый компонент ядра |
|
||||
| Стандартный Runner | Go, Docker Engine | Готовый исполнитель с адаптером |
|
||||
| Собственный Runner | Любой язык, HTTPS Runner Protocol v1 | Независимая реализация; требуется проверка совместимости |
|
||||
| Локальный клиент | Go CLI, Docker Engine | Общий resolver и ExecutionSpec; локальное состояние в файлах |
|
||||
| Хранилища | PostgreSQL; S3-совместимое хранилище | Отдельные базы, buckets и сервисные учётные записи |
|
||||
| Secrets | Envelope encryption, KMS; локально OS keychain | Провайдер выбирается при развёртывании |
|
||||
| Шлюз инструментов | Go, HTTPS | Опциональный новый сервис |
|
||||
| Наблюдаемость | OpenTelemetry, Prometheus, Grafana, Loki | Интеграция; без payload и секретов по умолчанию |
|
||||
| Начальное развёртывание | Linux, Docker Compose, Nginx | Управление и исполнители на разных машинах |
|
||||
|
||||
Runtime внутри harness’а не ограничен языком платформы. Kubernetes — будущая реализация Runner, не зависимость MVP. Kafka и отдельный координатор кластера в MVP не требуются.
|
||||
|
||||
## Публикация пакета
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant A as Автор
|
||||
participant C as Каталог
|
||||
participant S as Package Storage / Registry
|
||||
participant D as Catalog DB
|
||||
A->>C: Создать draft и запросить upload
|
||||
C-->>A: Ограниченный адрес загрузки
|
||||
A->>S: Загрузить архив или OCI-образ
|
||||
A->>C: Publish с digest и манифестом
|
||||
C->>S: Проверить наличие, размер и digest
|
||||
C->>C: Проверить схему и зависимости
|
||||
C->>D: Зафиксировать immutable version
|
||||
C-->>A: Published version
|
||||
```
|
||||
|
||||
Метаданные опубликованы только после проверки объекта. Незавершённые загрузки убирает сборщик мусора после grace period. Каталог хранит четыре типа пакетов: harness, plugin, skill, template. Версии неизменяемы; блокировка — изменяемая политика доступа поверх версии. Детали: [каталог](catalog.md).
|
||||
|
||||
## Настройка через JSON Schema
|
||||
|
||||
Автор шаблона задаёт config.schema.json, config.defaults.json и необязательную ui.schema.json. Схемы настроек harness’а и плагинов подключаются шаблоном в отдельные пространства имён. Пользователь меняет значения, не схему автора. Secret-поля содержат ссылки на подключения, а не значения credentials.
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant U as Пользователь
|
||||
participant W as Web / локальная форма
|
||||
participant C as Каталог
|
||||
participant B as Platform API / локальный валидатор
|
||||
W->>C: Загрузить закреплённую схему и defaults
|
||||
C-->>W: JSON Schema и UI Schema
|
||||
W-->>U: Форма настроек
|
||||
U->>W: Значения и ссылки на подключения
|
||||
W->>B: Сохранить конфигурацию
|
||||
B->>B: Проверить схему, права и policy
|
||||
B-->>W: Новая immutable revision
|
||||
```
|
||||
|
||||
Сервер повторно валидирует данные. Скрытое поле формы не является защитой. Defaults применяются отдельным шагом: JSON Schema default не заполняет данные автоматически. [Конфигурация и UI](configuration-ui.md).
|
||||
|
||||
## Локальный запуск
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant U as Пользователь
|
||||
participant L as Local client
|
||||
participant C as Каталог / локальный кеш
|
||||
participant R as Local Runner
|
||||
participant H as Harness
|
||||
U->>L: Запустить экземпляр агента
|
||||
L->>C: Resolve и проверить digest
|
||||
L->>L: Валидировать config, собрать ExecutionSpec
|
||||
L->>R: Spec и локальные secret bindings
|
||||
R->>H: Запустить в ограниченном окружении
|
||||
H-->>R: События и результат
|
||||
R-->>L: Итог и локальные файлы
|
||||
```
|
||||
|
||||
## Удалённый запуск
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant U as Пользователь
|
||||
participant B as Platform API
|
||||
participant C as Каталог
|
||||
participant D as Диспетчер
|
||||
participant R as Runner / адаптер
|
||||
participant S as Хранилища
|
||||
U->>B: Создать Run с Idempotency-Key
|
||||
B->>C: Resolve точных версий
|
||||
B->>B: Сохранить Run и outbox в одной транзакции
|
||||
D->>B: Обработать готовый Run
|
||||
R->>D: Claim с capabilities
|
||||
D-->>R: Attempt, lease, ExecutionSpec
|
||||
R->>S: Загрузить пакеты по digest
|
||||
R->>D: Heartbeat и события
|
||||
R->>S: Загрузить результаты
|
||||
R->>D: Complete с lease epoch и manifest результатов
|
||||
D->>B: Финализировать Run
|
||||
B-->>U: Статус и доступ к файлам
|
||||
```
|
||||
|
||||
Обработка допускает повторную доставку; exactly-once внешних действий не обещается. Отзыв lease блокирует поздние записи в платформу, но не может отменить уже отправленный запрос во внешний сервис. [Исполнение](execution.md).
|
||||
|
||||
## Авторизация
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant U as Пользователь
|
||||
participant I as Identity
|
||||
participant B as API
|
||||
participant R as Runner
|
||||
participant T as Tool gateway
|
||||
participant S as Secrets
|
||||
U->>I: Вход
|
||||
I-->>U: Сессия
|
||||
U->>B: Создать запуск
|
||||
B->>B: Tenant, роль, пакет, подключения, квоты
|
||||
B-->>R: Токен только назначенного Attempt
|
||||
R->>T: Разрешённая операция
|
||||
T->>B: Проверить права и актуальность Attempt
|
||||
T->>S: Получить нужный credential
|
||||
T-->>R: Результат без долгоживущего ключа
|
||||
```
|
||||
|
||||
Прямые интеграции разрешаются явно: секрет может потребоваться внутри окружения. В таком режиме runner-owner становится частью границы доверия, а стоимость внешних вызовов нельзя ограничить только платформенным счётчиком. [Доступы и изоляция](security.md).
|
||||
|
||||
## Состояния запуска
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> queued
|
||||
queued --> preparing: назначение
|
||||
preparing --> running: окружение готово
|
||||
running --> waiting_input: checkpoint и запрос ввода
|
||||
waiting_input --> queued: подтверждённое продолжение
|
||||
running --> succeeded: результат принят
|
||||
preparing --> failed: ошибка подготовки
|
||||
running --> failed: ошибка
|
||||
preparing --> interrupted: потеря lease
|
||||
running --> interrupted: потеря lease
|
||||
interrupted --> queued: разрешённый повтор
|
||||
interrupted --> failed: повтор запрещён
|
||||
queued --> cancelled: отмена
|
||||
waiting_input --> cancelled: отмена
|
||||
preparing --> cancelling: запрос отмены
|
||||
running --> cancelling: запрос отмены
|
||||
cancelling --> cancelled: остановка подтверждена
|
||||
cancelling --> interrupted: остановка не подтверждена
|
||||
succeeded --> [*]
|
||||
failed --> [*]
|
||||
cancelled --> [*]
|
||||
```
|
||||
|
||||
Переход в waiting_input освобождает исполнителя только после durable checkpoint. Для harness без checkpoint этот режим не предоставляется. При потере lease от собственного Runner результат внешних действий может остаться неизвестным.
|
||||
|
||||
## Замена исполнителей
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
D["Диспетчер / tenant policy, capabilities, quotas"]
|
||||
P["Runner Protocol v1 / HTTPS"]
|
||||
A["Адаптер стандартного движка"]
|
||||
R["Собственный Runner"]
|
||||
K["Kubernetes Runner / после MVP"]
|
||||
L["Local Runner"]
|
||||
D --> P
|
||||
P --> A
|
||||
P --> R
|
||||
P --> K
|
||||
P --> L
|
||||
```
|
||||
|
||||
Нативный Runner и адаптер не создают две независимые аренды одного Attempt: платформа хранит lease, адаптер сопоставляет его с executionId движка и подавляет дубликаты. Локальный автономный режим использует тот же ExecutionSpec, но не обязан регистрироваться в облаке. [Протокол Runner](api/executor-api.md).
|
||||
|
||||
## Масштабирование
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
API["API replicas"] --> DB[("PostgreSQL / runs + outbox")]
|
||||
D["Dispatcher replicas / atomic claims"] --> DB
|
||||
D --> Q["Логические очереди tenants / fair scheduling"]
|
||||
Q --> A["Управляемый пул"]
|
||||
Q --> B["Собственные пулы tenants"]
|
||||
A --> S[("S3 + локальные кеши образов")]
|
||||
B --> S
|
||||
A --> M["Внешние API / rate limits"]
|
||||
B --> M
|
||||
```
|
||||
|
||||
Ёмкость определяется активными запусками, длительностью и ресурсами; добавление исполнителей ограничено квотами внешних API. Реализации движка с одной активной репликой не масштабируются простым дублированием процесса: сначала проверяются ограничения ядра, затем шардирование по пулам. Формулы, отказы, SLO и retention: [масштабирование](scaling.md).
|
||||
|
||||
## MVP и развитие
|
||||
|
||||
MVP: четыре типа пакетов; immutable versions; конфигурация через формы; экземпляры агентов; ручной и регулярный запуск; стандартный и локальный Runner; документированный протокол своей реализации; отмена, события и результаты; tenant isolation; registry и buckets; аудит.
|
||||
|
||||
Следующий этап: возобновление через checkpoint, пользовательский ввод, автоматическая миграция конфигураций, Kubernetes Runner, автоматическое масштабирование и коммерческий биллинг. Протокол заранее резервирует эти возможности, но UI не предлагает их без подтверждённой capability.
|
||||
|
||||
Открытые решения: провайдер KMS и S3, конкретные версии зависимостей, политика модерации публичных пакетов, целевые SLO после нагрузочных измерений, перечень лицензий распространяемых runtime и пакетов. Публичный доступ к этому проекту сам по себе не задаёт лицензию его дальнейшего использования.
|
||||
|
||||
## Подробные спецификации
|
||||
|
||||
- [Каталог и версии](catalog.md)
|
||||
- [JSON Schema Driven UI](configuration-ui.md)
|
||||
- [Исполнение](execution.md)
|
||||
- [Авторизация и изоляция](security.md)
|
||||
- [Масштабирование](scaling.md)
|
||||
- [Общие правила API](api/conventions.md)
|
||||
- [API каталога](api/catalog-api.md)
|
||||
- [API агентов](api/agents-api.md)
|
||||
- [API запусков](api/runs-api.md)
|
||||
- [Runner Protocol](api/executor-api.md)
|
||||
- [Модель данных и JSON Schema](contracts/domain-model.md)
|
||||
@@ -0,0 +1,35 @@
|
||||
# Исполнение
|
||||
|
||||
## Граница чёрного ящика
|
||||
|
||||
Harness получает ExecutionSpec и разрешённые ресурсы. Его внутренний цикл не управляется платформой. Для участия в платформе пакет предоставляет entrypoint и адаптер событий. Exit code 0 означает успешное завершение процесса, но Run становится succeeded только после проверки ResultManifest и сохранения артефактов. Ошибка финансового рассуждения не определяется статусом процесса.
|
||||
|
||||
## Планирование
|
||||
|
||||
API в одной транзакции записывает Run, reservation квоты и outbox. Диспетчер обрабатывает outbox идемпотентно по runId. Resolve выполняется до создания снимка; назначение повторно проверяет block policy и права. Попытка захватывается атомарно с leaseEpoch. Стандартный execution adapter использует runId/attemptId как ключ дедупликации, хранит соответствие с executionId движка и сверяет состояние при неоднозначном ответе.
|
||||
|
||||
Расписания хранят IANA timezone, cron expression, nextRunAt и overlapPolicy: skip или queue. В MVP catch-up после простоя создаёт не более одного пропущенного запуска. Уникальный ключ scheduleId + scheduledAt предотвращает дубли. При переходах DST пропущенное локальное время пропускается, повторённое выполняется один раз; scheduledAt фиксируется как UTC instant. Ручной запуск имеет отдельный ключ.
|
||||
|
||||
## Lease и повторы
|
||||
|
||||
Начальные настройки: lease 90 секунд, heartbeat каждые 20 секунд, maxAttempts 3 только для явно retry-safe задач. Важна серверная шкала времени. Каждый heartbeat и terminal report содержит attemptId, leaseEpoch и sequence. Устаревший epoch возвращает 409 и не может изменить состояние Run.
|
||||
|
||||
Потеря lease переводит Run в interrupted. Автоповтор разрешён, только если template объявил retrySafe и policy организации позволяет; иначе нужен явный retry пользователя после предупреждения о возможном повторе внешних действий. Checkpoint continuation создаёт новый Attempt того же Run. Retry terminal Run создаёт новый Run с parentRunId и тем же снимком, но с актуальной проверкой прав.
|
||||
|
||||
Fencing защищает записи платформы, но не останавливает старый процесс на недоступной машине и не обеспечивает exactly-once сторонних API. Повторные внешние действия должен защищать сам harness или поддерживающий idempotency key шлюз.
|
||||
|
||||
## Отмена и ожидание
|
||||
|
||||
Cancel queued снимает reservation и сразу завершает Run. Cancel running устанавливает cancelling; Runner прекращает процесс и возвращает подтверждение. Для управляемого Runner после grace period выполняется принудительная остановка. Без подтверждения собственного Runner состояние становится interrupted, а не falsely cancelled. Поздний success после принятой отмены не отменяет запрос пользователя.
|
||||
|
||||
Waiting_input поддерживается только с capability checkpoint-v1: Runner сохраняет checkpoint, передаёт его digest и input schema, получает подтверждение, затем освобождает lease. Ответ пользователя валидируется, разрешение проверяется заново. Повтор responseId не создаёт ещё одну попытку. Новое продолжение закрепляется за исходным ExecutionSpec.
|
||||
|
||||
## Локальный режим
|
||||
|
||||
Клиент скачивает lock и артефакты, проверяет digest и разрешения, сохраняет ExecutionSpec, запускает локальный Runner. Credentials поступают из OS keychain или выбранного пользователем провайдера. Облачная регистрация не обязательна для кешированных публичных пакетов. Синхронизация результатов с облаком — отдельное явное действие, не поведение по умолчанию.
|
||||
|
||||
## Артефакты
|
||||
|
||||
Runner запрашивает upload grant для конкретного Attempt и назначения файла. Grant ограничен размером, сроком и префиксом. ResultManifest содержит artifactId, digest, size и mediaType, без произвольных URL. Сервис проверяет существование объектов до terminal success. Логи доставляются отдельно и не являются единственным источником состояния.
|
||||
|
||||
[Runner Protocol](api/executor-api.md) · [Состояния и диаграммы](design.md).
|
||||
@@ -0,0 +1,58 @@
|
||||
# Масштабирование Invest Harness
|
||||
|
||||
## Модель нагрузки
|
||||
|
||||
Число аккаунтов не определяет ёмкость. Измеряем arrival rate λ, среднюю и p95 длительность T, CPU и память Attempt, размер образа, объём логов, количество внешних запросов и число одновременных подключений. Раздельные классы: короткие интерактивные задачи, длинные фоновые, периодические и сборка пакетов.
|
||||
|
||||
По Little’s law среднее число активных задач L = λ × T. Для целевой загрузки слотов ρ начальная оценка slots ≥ λ × T / ρ. Пример-гипотеза: 120 запусков/мин, 180 секунд на запуск, ρ=0.7 → около 515 слотов. Это не 515 серверов: число слотов на узле ограничено одновременно CPU, RAM, диском и квотами API. Хвосты длительности требуют нагрузочного измерения, а не подстановки p95 в доказательство SLO.
|
||||
|
||||
## Очереди и backpressure
|
||||
|
||||
Admission control резервирует tenant concurrency и бюджет перед назначением. Weighted fair scheduling чередует tenants; внутри tenant приоритет + возраст. Ограничены queue depth и max queue age. При превышении пользователь получает 429 с Retry-After либо явный queued status по принятой policy. Бесконечного скрытого backlog быть не должно.
|
||||
|
||||
Учитываем лимиты внешних API, токены/мин, соединения и доступность источников. Шлюз контролирует свои вызовы. Для прямых вызовов Runner платформа гарантирует только ресурсные/временные лимиты, а финансовый предел зависит от провайдера credentials.
|
||||
|
||||
## Масштабирование компонентов
|
||||
|
||||
| Компонент | Стратегия | Ограничение |
|
||||
|---|---|---|
|
||||
| Stateless API и каталог | Несколько реплик за Gateway | Пулы SQL и согласованность прав |
|
||||
| Диспетчер | Atomic claims, partitioning по tenant/pool | Единственное назначение Attempt и fencing |
|
||||
| Сервис исполнения | Сначала ресурсы и отдельные пулы, затем проверенное шардирование | Нельзя предполагать поддержку active-active ядром |
|
||||
| Runner | Масштабирование по ready queue age и свободным ресурсам | Внешние API и время загрузки образа |
|
||||
| PostgreSQL | Индексы, bounded pagination, архивирование; read replicas для допустимо устаревших чтений | Назначения и авторизация читаются из актуального источника |
|
||||
| S3 / registry | Прямые scoped grants, кеш по digest, лимит параллельных загрузок | Bandwidth, egress, cache isolation |
|
||||
|
||||
Горячие индексы: Run(tenantId, createdAt, id), Run(status, nextEligibleAt, id), Attempt(poolId, leaseUntil), Package(tenantId, kind, name), уникальный PackageVersion(packageId, version). Большие события и логи не хранятся целиком в строке Run.
|
||||
|
||||
## Прогрев и масштабирование исполнителей
|
||||
|
||||
Managed pool хранит небольшой warm reserve. Scale-out — по времени ожидания совместимых задач, scale-in — после drain: новые задания не выдаются, активные завершаются. Кеш неизменяемых образов по digest экономит загрузки. Приватные образы и архивы не выдаются другому tenant без проверки доступа. Garbage collection кеша использует размер и last-used; pinned active layers не удаляются.
|
||||
|
||||
Собственный пул имеет независимые лимиты и SLA владельца. Недоступность такого пула не переносит задание автоматически в управляемый: credentials и политика размещения могут запрещать перенос.
|
||||
|
||||
## Отказы
|
||||
|
||||
| Сбой | Поведение |
|
||||
|---|---|
|
||||
| API после записи Run | Повтор Idempotency-Key возвращает прежний Run |
|
||||
| Dispatcher после отправки job | Reconcile по attemptId, без второго внешнего запуска |
|
||||
| Runner потерял связь | Lease expires → interrupted; retry только по policy |
|
||||
| S3 недоступен при завершении | Нет succeeded до проверки ResultManifest |
|
||||
| Catalog недоступен | Новые resolve ждут/ошибка; закреплённые запускать только при актуальной policy |
|
||||
| Registry недоступен | Кеш допустим при проверке digest и актуальных прав; иначе очередь |
|
||||
| SQL недоступен | Новые назначения остановлены; terminal reports повторяются идемпотентно |
|
||||
|
||||
## Retention и восстановление
|
||||
|
||||
Исходные настраиваемые значения: orphan uploads 24 часа; debug-логи 14 дней; результаты 30 дней; аудит 180 дней. Это проектные значения, а не юридические обязательства. Активные Run и checkpoints защищены от очистки. Образы удаляются только после проверки ссылок, grace period и доступности backup.
|
||||
|
||||
Backup включает БД, объекты, ключевые конфигурации и возможность восстановления ключей шифрования. Проверяется согласованное восстановление metadata → digest → object. Цели для первой проверки: RPO 1 час, RTO 4 часа, без обещания до rehearsal.
|
||||
|
||||
## Метрики и проверка
|
||||
|
||||
Предлагаемые цели пилота: p95 control API <500 мс без внешних вызовов; p95 queue wait <30 секунд при нагрузке в пределах квоты и доступном пуле. Время выполнения чёрного ящика не включается в API SLO. Следим за queue age, interrupted rate, attempts/run, event lag, orphan bytes, cache hit, SQL saturation и стоимостью на Run.
|
||||
|
||||
Нагрузочные сценарии: равномерный поток, массовый scheduled burst с jitter, один шумный tenant, недоступный внешний API, перезапуск dispatcher, потеря Runner, медленный S3, дубли terminal reports. Отдельно проверяем отсутствие чужих артефактов и credentials.
|
||||
|
||||
Стоимость = вычисления + хранение + egress + управляемые вызовы моделей. Цена прямых сторонних вызовов может быть неизвестна. Биллинг не строится исключительно на недоверенных отчётах пользовательского Runner.
|
||||
@@ -0,0 +1,31 @@
|
||||
# Доступы и изоляция
|
||||
|
||||
## Идентичность и роли
|
||||
|
||||
Tenant — персональное или организационное пространство. Роли: owner управляет доступами и удалением; admin — настройками и Runner; publisher — публикацией пакетов; operator — запуском и отменой доступных агентов; viewer — чтением разрешённых ресурсов. Сервер получает tenant из проверенного membership, а не доверяет tenantId в запросе. Каждый доступ к agent, run, package, connection и artifact проверяется отдельно.
|
||||
|
||||
Identity в MVP — модуль Platform API с существующими механизмами аккаунтов и сессий; внешний OIDC подключается при необходимости. Web использует HttpOnly Secure SameSite cookie и CSRF-защиту мутаций. CLI использует ограниченный токен; device authorization добавляется при поддержке identity-провайдером. Подписанный токен проверяется по issuer, audience, expiry и revocation policy.
|
||||
|
||||
## Исполнители
|
||||
|
||||
Владелец tenant создаёт одноразовый registration grant для пула. Runner обменивает grant на собственную служебную идентичность. Отзыв блокирует новые задания и доступы. Capabilities — заявление исполнителя, а не доказательство изоляции; управляемые пулы проходят conformance checks. Пользовательские пулы явно маркируются как customer-managed.
|
||||
|
||||
Каждому Attempt выдаётся короткоживущий токен с runId, attemptId, tenantId, leaseEpoch, audience и scope. Он не предоставляет чтение чужих Run и доступ к администрированию. Сервер проверяет активность lease при чувствительных операциях.
|
||||
|
||||
## Секреты и интеграции
|
||||
|
||||
ConnectionRef указывает на запись подключения. Секрет хранится с envelope encryption, ключ расшифровки управляется KMS. Значения не попадают в config, логи, схемы, артефакты и signed URL. Доступ ограничивается интеграцией и операцией. Шлюз по возможности вызывает API сам; для прямого доступа выдаются scoped credentials или секрет монтируется только в окружение конкретной попытки.
|
||||
|
||||
Own Runner имеет техническую возможность прочитать выданные ему credentials; эта граница доверия показывается владельцу при назначении подключений. Отзыв токена платформы не отзывает уже раскрытый сторонний ключ: для этого нужен revoke у провайдера.
|
||||
|
||||
## Изоляция
|
||||
|
||||
Управляемое выполнение: отдельная рабочая директория и контейнер, non-root, лимиты CPU/RAM/PIDs/диска/времени, отсутствие host mounts, privileged mode и Docker socket внутри job. Сетевые политики ограничивают обращения к внутренним адресам и metadata endpoints. Для недоверенного кода выбирается усиленный sandbox runtime; один обычный контейнер не считается полной защитой от побега.
|
||||
|
||||
Runner Protocol не навязывает реализацию sandbox, но диспетчер не назначает защищённые задачи пулу без нужной trust policy. Невозможность обеспечить ограничение должна приводить к отказу до запуска.
|
||||
|
||||
## Аудит и данные
|
||||
|
||||
Аудит: actor, tenant, action, resource, result, timestamp, requestId. Без содержимого prompts и secrets по умолчанию. История публикаций, выдачи credentials, назначения, отмены, обновления настроек и изменений прав сохраняется отдельно от debug-логов.
|
||||
|
||||
Удаление tenant сначала блокирует новые запуски, отзывает доступ, останавливает задачи и удаляет данные по retention policy. Backup retention и сроки окончательного удаления явно сообщаются пользователю. Публичность пакета не распространяется на Run, connections или результаты.
|
||||
Reference in new issue
Block a user