feat: complete Epic 1 production foundation
This commit is contained in:
@@ -19,6 +19,7 @@ Crank превращает REST API endpoint-ы в MCP-инструменты,
|
||||
- [Импорт OpenAPI](./openapi-import.md)
|
||||
- [Секреты и профили авторизации](./secrets-and-auth.md)
|
||||
- [Журналы и использование](./observability.md)
|
||||
- [Единая граница REST execution](./execution-boundary.md)
|
||||
- [Проектирование MCP-инструментов](./tool-design.md)
|
||||
|
||||
## Справочник
|
||||
|
||||
+138
-3
@@ -16,7 +16,17 @@ Auth path:
|
||||
|
||||
## Авторизация
|
||||
|
||||
Администратор входит по email и паролю. После входа сервер устанавливает HttpOnly session cookie.
|
||||
Администратор входит по email и паролю. После входа сервер устанавливает
|
||||
HttpOnly session cookie и возвращает `csrf_token` для browser mutations.
|
||||
|
||||
Первый production-admin создаётся через локальный одноразовый bootstrap token:
|
||||
|
||||
```bash
|
||||
crank-migrate admin-auth bootstrap-create --email owner@example.com
|
||||
```
|
||||
|
||||
После этого оператор открывает `/login`, вводит token и задаёт первый пароль.
|
||||
Token одноразовый; replay возвращает generic unauthorized без раскрытия причины.
|
||||
|
||||
```bash
|
||||
curl -i https://crank.example.com/api/auth/login \
|
||||
@@ -34,8 +44,14 @@ curl https://crank.example.com/api/auth/session \
|
||||
-b 'crank_session=<cookie_value>'
|
||||
```
|
||||
|
||||
Для небезопасных browser/API mutations с session cookie передавайте текущий
|
||||
`csrf_token` в заголовке `x-csrf-token`. Cross-origin `/api/*` requests
|
||||
отклоняются по умолчанию.
|
||||
|
||||
Endpoints:
|
||||
|
||||
- `GET /api/auth/bootstrap/status`
|
||||
- `POST /api/auth/bootstrap/complete`
|
||||
- `POST /api/auth/login`
|
||||
- `POST /api/auth/logout`
|
||||
- `GET /api/auth/session`
|
||||
@@ -97,6 +113,8 @@ curl https://crank.example.com/api/admin/workspaces/ws_default/upstreams \
|
||||
|
||||
Операция описывает один REST endpoint как MCP-инструмент.
|
||||
|
||||
Published Version неизменяема. Изменение после публикации создаёт следующую Draft revision; существующий Agent продолжает использовать exact bound version до явного rebinding и новой публикации Agent. Архивация запрещает новые изменения/привязки, но не удаляет опубликованные snapshots или историю.
|
||||
|
||||
- `GET /api/admin/workspaces/{workspace_id}/operations`
|
||||
- `POST /api/admin/workspaces/{workspace_id}/operations`
|
||||
- `POST /api/admin/workspaces/{workspace_id}/operations/analyze-quality`
|
||||
@@ -126,15 +144,26 @@ curl https://crank.example.com/api/admin/workspaces/ws_default/operations/<opera
|
||||
}'
|
||||
```
|
||||
|
||||
Успешные поля Test Run сохранены. Каждый элемент `errors` дополнительно содержит
|
||||
stable `code`, `stage`, `retryability`, `outcome_certainty` и безопасные
|
||||
Request/Trace IDs ответа. При `outcome_unknown` автоматический повтор запрещён;
|
||||
пользователь должен сверить результат во внешней системе.
|
||||
|
||||
Пример публикации:
|
||||
|
||||
```bash
|
||||
# Сначала прочитайте актуальный strong ETag из GET /operations/<operation_id>.
|
||||
curl https://crank.example.com/api/admin/workspaces/ws_default/operations/<operation_id>/publish \
|
||||
-b 'crank_session=<cookie_value>' \
|
||||
-H 'If-Match: "<opaque-operation-etag>"' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data '{ "version": 1 }'
|
||||
```
|
||||
|
||||
Mutation contract revision 2 требует `If-Match` для `PATCH`, create-version, publish, archive, delete и изменяющего существующую Operation YAML upsert. Отсутствующий token возвращает `428 operation_precondition_required`, устаревший или относящийся к другому объекту — `409 operation_stale_version`. Token повторно проверяется под тем же PostgreSQL row lock, что и mutation. Summary возвращает `can_delete`, а detail — aggregate `availability` отдельно от exact Draft/Published version state. Exact version reads имеют отдельный content-stable ETag.
|
||||
|
||||
Portable export использует закрытый `format_version: "2"` contract из [`schemas/operation-export-v2.schema.json`](schemas/operation-export-v2.schema.json). Он исключает persistence IDs, lifecycle metadata, samples, wizard state и credentials. Legacy v1 принимается только при импорте и нормализуется в v2; exporter v1 не выдаёт. YAML import ограничен 256 KiB и возвращает только bounded codes `operation_yaml_too_large|operation_yaml_invalid|operation_yaml_unsupported` без raw parser text.
|
||||
|
||||
`analyze-quality` принимает payload операции и возвращает рекомендации:
|
||||
|
||||
```json
|
||||
@@ -182,16 +211,21 @@ curl https://crank.example.com/api/admin/workspaces/ws_default/secrets \
|
||||
```
|
||||
|
||||
После создания или ротации API возвращает только metadata. Значение секрета нельзя прочитать повторно.
|
||||
Если secret используется Auth Profile, удаление отклоняется `409 secret_referenced_by_auth_profile`.
|
||||
Create, rotate, delete и denied-delete пишут bounded audit event с actor,
|
||||
credential ref, request id и trace id; plaintext, ciphertext и hash в event не
|
||||
попадают.
|
||||
|
||||
## Auth profiles
|
||||
|
||||
- `GET /api/admin/workspaces/{workspace_id}/auth-profiles`
|
||||
- `POST /api/admin/workspaces/{workspace_id}/auth-profiles`
|
||||
- `GET /api/admin/workspaces/{workspace_id}/auth-profiles/{auth_profile_id}`
|
||||
- `PATCH /api/admin/workspaces/{workspace_id}/auth-profiles/{auth_profile_id}`
|
||||
- `DELETE /api/admin/workspaces/{workspace_id}/auth-profiles/{auth_profile_id}`
|
||||
|
||||
Auth profile хранит ссылки на secrets и способ применения секрета к REST-запросу.
|
||||
При выполнении Operation текущая версия Secret читается перед dispatch; rotation
|
||||
Secret меняет credential для следующего execution без переписывания Operation
|
||||
или Auth Profile refs.
|
||||
|
||||
## Agents
|
||||
|
||||
@@ -254,6 +288,14 @@ curl https://crank.example.com/api/admin/workspaces/ws_default/agents \
|
||||
|
||||
Старый формат тела из одного массива привязок поддерживается и сохраняет текущую политику агента. Предварительная проверка принимает те же `bindings` и `tool_selection_policy`, а также `query` и необязательный `group_ids`.
|
||||
|
||||
Agent catalog lifecycle (`agent-catalog-lifecycle-v9`) защищает опубликованный каталог от in-place mutation:
|
||||
|
||||
- `GET /agents/{agent_id}` возвращает strong `ETag`, связанный с workspace, Agent identity, current Draft version, latest Published version, availability и `catalog_revision`;
|
||||
- mutation опубликованного Agent (`PATCH`, `DELETE`, `bindings`, `publish`, `unpublish`, `archive`) требует актуальный `If-Match`; отсутствующий precondition возвращает `428 agent_precondition_required`, устаревший — `409 agent_stale_revision`;
|
||||
- Published Agent Version и его bindings immutable на уровне registry/DB; изменение каталога создаёт новый Draft/current version и отдельный publish;
|
||||
- binding принимает только exact Published Operation Version из того же workspace; draft, archived, missing или foreign Operation Version отклоняются до publication;
|
||||
- `catalog_revision` монотонно растёт при publish/unpublish/archive и используется MCP search/call для защиты от stale search results.
|
||||
|
||||
## Agent API keys
|
||||
|
||||
- `GET /api/admin/workspaces/{workspace_id}/agents/{agent_id}/platform-api-keys`
|
||||
@@ -274,11 +316,69 @@ curl https://crank.example.com/api/admin/workspaces/ws_default/agents/<agent_id>
|
||||
```
|
||||
|
||||
Полное значение ключа доступно только в create response.
|
||||
Дальше API возвращает только metadata: `id`, `name`, bounded prefix, `key_kind`,
|
||||
`scopes`, `status` и timestamps. Hash и raw key никогда не возвращаются.
|
||||
|
||||
`key_kind = mcp_client` используется только для MCP client доступа.
|
||||
`key_kind = approval` используется только для approval side-channel. Для approval
|
||||
keys можно задать `allowed_origins`; значения должны быть точными
|
||||
`http://`/`https://` origins без path/query/userinfo. MCP approval запрос с
|
||||
чужим `Origin` отклоняется до исполнения side effect.
|
||||
|
||||
`revoke` немедленно прекращает доступ, включая уже существующие MCP session.
|
||||
`DELETE` переводит уже revoked key в terminal metadata state `deleted`, но не
|
||||
стирает provenance. Credential mutations пишут bounded audit event с actor,
|
||||
credential ref, request id и trace id; raw key/hash в audit event не попадают.
|
||||
|
||||
Create response для `mcp_client` key дополнительно содержит ephemeral
|
||||
`connection`: canonical MCP endpoint и copy-safe конфигурации для
|
||||
поддерживаемых representative clients. Это единственная граница, где API
|
||||
возвращает raw secret. При повторном `GET`, после обновления страницы, revoke
|
||||
или delete возвращаются только metadata; потерянное значение не восстанавливается.
|
||||
После неоднозначной ошибки create UI не должен автоматически повторять запрос:
|
||||
оператор сначала обновляет metadata, затем осознанно создаёт или ротирует key.
|
||||
|
||||
## Getting Started onboarding
|
||||
|
||||
- `GET /api/admin/workspaces/{workspace_id}/onboarding`
|
||||
- `POST /api/admin/workspaces/{workspace_id}/onboarding/events`
|
||||
|
||||
`GET` доступен только аутентифицированному участнику workspace и строит
|
||||
ограниченную server-authoritative проекцию: `operation`, `test`,
|
||||
`publish_operation`, `agent`, `key`, `mcp_connection`, `first_call`. Browser
|
||||
не передаёт completed state и не может завершить domain step. Первый read
|
||||
идемпотентно фиксирует server-owned eligible cohort, а terminal projection после
|
||||
реального успеха идемпотентно фиксирует completion. Оба времени выдаются в UTC
|
||||
RFC3339.
|
||||
|
||||
Ответ имеет `schema_version: 1`, opaque `revision`, `status`
|
||||
(`in_progress|complete`), `eligible_since`, упорядоченные `steps`, точные
|
||||
Operation/Agent/key references, canonical `mcp_endpoint` и, только после
|
||||
успешного public `tools/call`, `first_call`. Каждый step содержит stable
|
||||
`id`, `completed`, `status` (`pending|current|complete|regressed`),
|
||||
`action_code` и `reason_code`. В `first_call` есть только безопасные
|
||||
`log_id`, Agent/key/Operation/version, tool, timestamp, Request ID и Trace ID;
|
||||
входной payload, raw key и upstream body в snapshot не попадают.
|
||||
|
||||
`POST /onboarding/events` принимает только presentation events
|
||||
`started|resumed|dismissed|abandoned`, bounded `idempotency_key` и текущий
|
||||
opaque `expected_revision`. Unknown fields и попытки отправить `eligible`,
|
||||
`completed`, domain steps или client supplied cohort timestamp отклоняются.
|
||||
Конфликт revision возвращает `409 onboarding_stale_revision` с recovery
|
||||
`reload`; запрещённый event — `422 onboarding_event_not_allowed`. Повтор того
|
||||
же idempotency key безопасен и не меняет server-derived progress.
|
||||
|
||||
## Logs и usage
|
||||
|
||||
- `GET /api/admin/workspaces/{workspace_id}/logs`
|
||||
- `GET /api/admin/workspaces/{workspace_id}/logs/{log_id}`
|
||||
- `GET /api/admin/workspaces/{workspace_id}/logs/export.csv`
|
||||
- `GET /api/admin/workspaces/{workspace_id}/usage`
|
||||
- `GET /api/admin/workspaces/{workspace_id}/usage/export.csv`
|
||||
- `GET /api/admin/workspaces/{workspace_id}/usage/operations/{operation_id}`
|
||||
- `GET /api/admin/workspaces/{workspace_id}/usage/agents/{agent_id}`
|
||||
- `GET /api/admin/workspaces/{workspace_id}/approvals`
|
||||
- `GET /api/admin/workspaces/{workspace_id}/approvals/{approval_id}`
|
||||
|
||||
Пример:
|
||||
|
||||
@@ -287,6 +387,41 @@ curl 'https://crank.example.com/api/admin/workspaces/ws_default/logs?limit=20' \
|
||||
-b 'crank_session=<cookie_value>'
|
||||
```
|
||||
|
||||
Logs list принимает bounded filters `period`, `created_after`,
|
||||
`created_before`, `level`, `status`, `outcome_group`, `source`, `operation_id`,
|
||||
`agent_id`, `search`, `limit` и opaque `cursor`. Explicit
|
||||
`created_after/created_before` задают UTC RFC3339 half-open window `[start,
|
||||
end)` и должны передаваться парой. Ответ имеет форму `{ "items": [...],
|
||||
"next_cursor": "..." | null }`; cursor привязан к детерминированному порядку
|
||||
`created_at desc, id desc` и не раскрывает host path или секреты. Log detail
|
||||
возвращает безопасные preview-поля, `request_id`, `trace_id`, execution
|
||||
taxonomy, точную Operation Version и связанную Operation/Agent metadata только
|
||||
в рамках текущего workspace.
|
||||
|
||||
`logs/export.csv` применяет те же auth, scope и filters, что и list endpoint.
|
||||
CSV ограничен по строкам/размеру, использует уже отредактированные previews и
|
||||
экранирует spreadsheet-formula значения (`=`, `+`, `-`, `@`, tab, CR/LF в
|
||||
начале cell). CSV остаётся локальным Admin response; Crank не отправляет usage
|
||||
наружу.
|
||||
|
||||
Usage endpoints используют UTC half-open interval `[start, end)`. Они
|
||||
принимают либо bounded `period`, либо explicit RFC3339
|
||||
`created_after/created_before` пару. Overview возвращает workspace summary,
|
||||
timeline, breakdown по Operation/Agent и outcome группы: `success`, `upstream`,
|
||||
`client`, `schema`, `crank`. Эти группы позволяют отличать ошибки внешнего
|
||||
upstream или пользовательского input от ошибок самого Crank; `request_id` и
|
||||
`trace_id` не используются как labels или aggregation keys. `usage/export.csv`
|
||||
создаётся сервером из того же scoped usage dataset, имеет bounded размер,
|
||||
экранирует spreadsheet-formula значения и не зависит от текущего client-side
|
||||
snapshot браузера.
|
||||
|
||||
Admin API approvals являются read-only operational view. Approve/deny выполняет
|
||||
MCP approval side-channel с отдельным `approval` key, потому что этот ключ можно
|
||||
ограничить конкретным Agent и `allowed_origins`. Admin list/get показывает
|
||||
только bounded safe request summary и terminal response payload. Raw request
|
||||
payload, approval key, auth headers, confirmation/control tokens и secret-like
|
||||
значения не возвращаются.
|
||||
|
||||
## Ошибки
|
||||
|
||||
Admin API возвращает JSON-ошибки с человекочитаемым сообщением и контекстом, если он доступен.
|
||||
|
||||
@@ -3,29 +3,29 @@
|
||||
{
|
||||
"kind": "inventory",
|
||||
"path": "docs/capability-inventory.json",
|
||||
"sha256": "3d61a74e5e9455c526c3769a551508a21647923b0048397704e91f609c07f240"
|
||||
"sha256": "9abb9106812cc0a82e98e39cf9646aade1355495dea737c86ee95cc6d514e2ef"
|
||||
},
|
||||
{
|
||||
"kind": "required_surfaces",
|
||||
"path": "docs/capability-baseline/required-surfaces.json",
|
||||
"sha256": "9998ec2138d86f9963b36ba01885e09494a2fa12fb24bd0035e1c7de9d2c5ba9"
|
||||
"sha256": "223d93647c0ca42e55dbf021487a25181855871845c09bfeff50d9e945ee5a52"
|
||||
},
|
||||
{
|
||||
"kind": "taxonomy",
|
||||
"path": "docs/capability-baseline/outcome-taxonomy.json",
|
||||
"sha256": "2aee4fad1b4bfeccb0b46ec5688d69badac336dd632d03b2133e05b0dd3e301e"
|
||||
"sha256": "2f82560d8578d8a8cb1ec130abdd97749083ded2527c5e77ebc2fd3a8b5cc885"
|
||||
},
|
||||
{
|
||||
"kind": "checklist",
|
||||
"path": "docs/capability-baseline/manual-checklist.md",
|
||||
"sha256": "6f61b9b5995f977f92914ed11fb9d3b554cf9c66188b37d0a11c019918ee7c4b"
|
||||
"sha256": "b6981d51dd10805347bd6a131c720c56c5c2782d79dfa8b9493084f85f8a184a"
|
||||
},
|
||||
{
|
||||
"kind": "results",
|
||||
"path": "docs/capability-baseline/results.json",
|
||||
"sha256": "32f9a263866a0823c781c14231a21a5ca3be9a57f391e06065ce17bd04e03b80"
|
||||
"sha256": "150f7bf5c75893ea12348332b0940c97e90f5029cbd25ebcb1a8870a734ca2b6"
|
||||
}
|
||||
],
|
||||
"baseline_version": "2026.08.14.4",
|
||||
"baseline_version": "2026.08.24.1",
|
||||
"schema_version": 1
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Community UI capability baseline checklist
|
||||
|
||||
baseline_version: 2026.08.14.4
|
||||
baseline_version: 2026.08.24.1
|
||||
|
||||
This is a bounded brownfield baseline, not the release-candidate regression from Epic 8. For every charter record happy, loading, empty, error, recovery, stale-response, RU/EN, and safe-output observations. Use `not_run` for an applicable unexecuted state, `gap` for a missing required contract, and `n/a` only with a by-design reason.
|
||||
|
||||
@@ -87,3 +87,10 @@ This is a bounded brownfield baseline, not the release-candidate regression from
|
||||
- states: safe-output, error
|
||||
- verdict: fail
|
||||
- reason: Credential-shaped API error canaries were visible in multiple browser error states; see DEF-UI-003.
|
||||
|
||||
## UI-13 Getting Started to authoritative first Tool call
|
||||
|
||||
- flow_id: ui-onboarding-first-call
|
||||
- states: happy, loading, empty, error, recovery, stale, ru-en, safe-output
|
||||
- verdict: not_run
|
||||
- reason: The five-participant fresh-install usability protocol is documented, but no participant run or clean-revision evidence has been collected. Do not infer a ≤15-minute median, completion rate, or secret-safety observation from automated source changes.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"baseline_version": "2026.08.14.4",
|
||||
"baseline_version": "2026.08.24.1",
|
||||
"evidence_modes": [
|
||||
"automated",
|
||||
"manual_only"
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
{
|
||||
"baseline_version": "2026.08.14.4",
|
||||
"baseline_version": "2026.08.24.1",
|
||||
"required_flow_ids": [
|
||||
"api-agent-catalog",
|
||||
"api-approvals",
|
||||
"api-auth-profiles-upstreams-secrets",
|
||||
"api-auth-workspace",
|
||||
"api-canonical-request-trace-identity",
|
||||
"api-master-key-identity-rotation",
|
||||
"api-typed-metrics-foundation",
|
||||
"api-logs-usage",
|
||||
"api-mcp-approval-keys",
|
||||
@@ -25,7 +26,9 @@
|
||||
"ui-operation-import-export",
|
||||
"ui-operation-lifecycle",
|
||||
"ui-operation-test",
|
||||
"ui-usage"
|
||||
"ui-usage",
|
||||
"api-onboarding-first-call",
|
||||
"ui-onboarding-first-call"
|
||||
],
|
||||
"surface_groups": [
|
||||
{
|
||||
@@ -61,7 +64,8 @@
|
||||
{
|
||||
"flow_ids": [
|
||||
"ui-auth-profiles-upstreams-secrets",
|
||||
"api-auth-profiles-upstreams-secrets"
|
||||
"api-auth-profiles-upstreams-secrets",
|
||||
"api-master-key-identity-rotation"
|
||||
],
|
||||
"id": "credentials"
|
||||
},
|
||||
@@ -97,6 +101,13 @@
|
||||
"mcp-scoped-tool-search"
|
||||
],
|
||||
"id": "mcp-tools"
|
||||
},
|
||||
{
|
||||
"flow_ids": [
|
||||
"api-onboarding-first-call",
|
||||
"ui-onboarding-first-call"
|
||||
],
|
||||
"id": "onboarding"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"baseline_version": "2026.08.14.4",
|
||||
"baseline_version": "2026.08.24.1",
|
||||
"defects": [
|
||||
{
|
||||
"contract": "Local Playwright stack starts only after the configured database exists and is stable.",
|
||||
@@ -195,6 +195,15 @@
|
||||
"ui-logs-approvals"
|
||||
],
|
||||
"next_evidence": "Repeat the bounded canary pass after DEF-UI-003 is fixed."
|
||||
},
|
||||
{
|
||||
"check_id": "UI-13",
|
||||
"evidence_mode": "manual_only",
|
||||
"execution_verdict": "not_run",
|
||||
"flow_ids": [
|
||||
"ui-onboarding-first-call"
|
||||
],
|
||||
"next_evidence": "Run the five-participant fresh-install protocol in docs/manual-regression-checklist.md after a clean Story 1.16 revision; record all censored non-completers and separate eligible denominator."
|
||||
}
|
||||
],
|
||||
"runs": [
|
||||
@@ -213,7 +222,7 @@
|
||||
],
|
||||
"id": "run-ui-build-7a7f63c86e0e",
|
||||
"source_report_sha256": "7a7f63c86e0ef376bee32d4ed202a086b1f16710b1d3b0cc70a21c3475367e2d",
|
||||
"source_revision": "b7face0e9462877e11fa3eef2da2b32c1257eaeb",
|
||||
"source_revision": "767428436d3a54e60809a727044dfcec48e1f1dd",
|
||||
"summary": {
|
||||
"exit_code": 0,
|
||||
"skipped": 0,
|
||||
@@ -235,7 +244,7 @@
|
||||
],
|
||||
"id": "run-ui-playwright-60a3cd095202",
|
||||
"source_report_sha256": "60a3cd095202739e8d317cd38464d3dace4d02fadb57fbc13f2aaef25d0359cf",
|
||||
"source_revision": "b7face0e9462877e11fa3eef2da2b32c1257eaeb",
|
||||
"source_revision": "767428436d3a54e60809a727044dfcec48e1f1dd",
|
||||
"summary": {
|
||||
"failed": 0,
|
||||
"flaky": 0,
|
||||
@@ -261,13 +270,6 @@
|
||||
"mcp-transport-session"
|
||||
],
|
||||
"id": "run-authenticated-product-smoke-20a893677c88",
|
||||
"source_report_sha256": "20a893677c8849d1e0ab7255a1ee54cd591e6b87e9678ebf64bc551f87abbd4b",
|
||||
"source_revision": "b7face0e9462877e11fa3eef2da2b32c1257eaeb",
|
||||
"summary": {
|
||||
"exit_code": 0,
|
||||
"skipped": 0,
|
||||
"timed_out": false
|
||||
},
|
||||
"safe_outcome": {
|
||||
"agent_id": "agent_019fffea9a327832bf3c30d0496b9426",
|
||||
"agent_revision": 1,
|
||||
@@ -282,6 +284,13 @@
|
||||
"mcp_call"
|
||||
],
|
||||
"verdict": "pass"
|
||||
},
|
||||
"source_report_sha256": "20a893677c8849d1e0ab7255a1ee54cd591e6b87e9678ebf64bc551f87abbd4b",
|
||||
"source_revision": "767428436d3a54e60809a727044dfcec48e1f1dd",
|
||||
"summary": {
|
||||
"exit_code": 0,
|
||||
"skipped": 0,
|
||||
"timed_out": false
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -311,13 +320,71 @@
|
||||
],
|
||||
"id": "run-just-verify-4f6cd6c62692",
|
||||
"source_report_sha256": "4f6cd6c62692b95de705bf1c382a412d001f3e98470e4f1b6b4c16d96b625995",
|
||||
"source_revision": "b7face0e9462877e11fa3eef2da2b32c1257eaeb",
|
||||
"source_revision": "767428436d3a54e60809a727044dfcec48e1f1dd",
|
||||
"summary": {
|
||||
"exit_code": 0,
|
||||
"skipped": 0,
|
||||
"timed_out": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"accepted": true,
|
||||
"collector": "capability-baseline-collector-v1",
|
||||
"command_id": "rust-admin-integration",
|
||||
"environment_class": "community-test",
|
||||
"evidence_mode": "automated",
|
||||
"execution_verdict": "pass",
|
||||
"flow_ids": [
|
||||
"api-auth-profiles-upstreams-secrets",
|
||||
"api-operation-lifecycle",
|
||||
"api-operation-test-run"
|
||||
],
|
||||
"id": "run-credential-lifecycle-59bcb7a400b9",
|
||||
"safe_outcome": {
|
||||
"command": "cargo test -p admin-api --test integration integration::credential_lifecycle -- --test-threads=1",
|
||||
"credential_lifecycle": "pass",
|
||||
"secret_material_exposed": false,
|
||||
"verdict": "pass"
|
||||
},
|
||||
"source_report_sha256": "59bcb7a400b91025fdb90fdf5a8d623dfa3f1bd54581522b640ae3d6532c554e",
|
||||
"source_revision": "767428436d3a54e60809a727044dfcec48e1f1dd",
|
||||
"summary": {
|
||||
"exit_code": 0,
|
||||
"failed": 0,
|
||||
"passed": 8,
|
||||
"skipped": 0,
|
||||
"timed_out": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"accepted": true,
|
||||
"collector": "capability-baseline-collector-v1",
|
||||
"command_id": "rust-admin-integration",
|
||||
"environment_class": "community-test",
|
||||
"evidence_mode": "automated",
|
||||
"execution_verdict": "pass",
|
||||
"flow_ids": [
|
||||
"api-master-key-identity-rotation"
|
||||
],
|
||||
"id": "run-master-key-rotation-94df00120348",
|
||||
"safe_outcome": {
|
||||
"admin_startup_mismatch": "pass",
|
||||
"cli_preflight_resume_verify_promote": "pass",
|
||||
"mcp_startup_mismatch": "pass",
|
||||
"registry_rotation_abort_resume_promote": "pass",
|
||||
"secret_material_exposed": false,
|
||||
"verdict": "pass"
|
||||
},
|
||||
"source_report_sha256": "94df0012034871bc5a6c65e7fa6a5e0f9eb14e2d651916cecff2b764fba5cefa",
|
||||
"source_revision": "767428436d3a54e60809a727044dfcec48e1f1dd",
|
||||
"summary": {
|
||||
"exit_code": 0,
|
||||
"failed": 0,
|
||||
"passed": 28,
|
||||
"skipped": 0,
|
||||
"timed_out": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"source_revision": "b7face0e9462877e11fa3eef2da2b32c1257eaeb"
|
||||
"source_revision": "767428436d3a54e60809a727044dfcec48e1f1dd"
|
||||
}
|
||||
|
||||
+644
-105
@@ -1,324 +1,863 @@
|
||||
{
|
||||
"flows": [
|
||||
{
|
||||
"capabilities": ["tools"],
|
||||
"evidence": {"automated": ["crates/crank-metrics/tests/schema_v1.rs", "crates/crank-metrics/tests/cardinality.rs", "crates/crank-observability/tests/http_metrics.rs"], "manual": ["docs/observability.md"]},
|
||||
"capabilities": [
|
||||
"tools"
|
||||
],
|
||||
"evidence": {
|
||||
"automated": [
|
||||
"crates/crank-metrics/tests/schema_v1.rs",
|
||||
"crates/crank-metrics/tests/cardinality.rs",
|
||||
"crates/crank-observability/tests/http_metrics.rs"
|
||||
],
|
||||
"manual": [
|
||||
"docs/observability.md"
|
||||
]
|
||||
},
|
||||
"id": "api-typed-metrics-foundation",
|
||||
"owner": "observability-community",
|
||||
"requirements": ["FR-32", "FR-46"],
|
||||
"requirements": [
|
||||
"FR-32",
|
||||
"FR-46"
|
||||
],
|
||||
"status": "implemented",
|
||||
"type": "api",
|
||||
"user_outcome": "Operators receive a versioned low-cardinality technical metrics contract with bounded Prometheus and OpenMetrics exposition."
|
||||
},
|
||||
{
|
||||
"capabilities": ["tools"],
|
||||
"evidence": {"automated": ["apps/ui/tests/e2e/login.spec.js", "apps/ui/tests/e2e/workspace-settings.spec.js"], "manual": ["docs/capability-baseline/manual-checklist.md"]},
|
||||
"capabilities": [
|
||||
"tools"
|
||||
],
|
||||
"evidence": {
|
||||
"automated": [
|
||||
"apps/ui/tests/e2e/login.spec.js",
|
||||
"apps/ui/tests/e2e/workspace-settings.spec.js"
|
||||
],
|
||||
"manual": [
|
||||
"docs/capability-baseline/manual-checklist.md"
|
||||
]
|
||||
},
|
||||
"id": "ui-auth-workspace",
|
||||
"owner": "admin-ui-community",
|
||||
"requirements": ["FR-3", "FR-46"],
|
||||
"requirements": [
|
||||
"FR-3",
|
||||
"FR-46"
|
||||
],
|
||||
"status": "implemented",
|
||||
"type": "ui",
|
||||
"user_outcome": "An administrator can authenticate and work in the selected Community workspace."
|
||||
},
|
||||
{
|
||||
"capabilities": ["tools"],
|
||||
"evidence": {"automated": ["apps/ui/tests/e2e/operations.spec.js", "apps/ui/tests/e2e/wizard.spec.js"], "manual": ["docs/capability-baseline/manual-checklist.md"]},
|
||||
"capabilities": [
|
||||
"tools"
|
||||
],
|
||||
"evidence": {
|
||||
"automated": [
|
||||
"apps/ui/tests/e2e/operations.spec.js",
|
||||
"apps/ui/tests/e2e/wizard.spec.js",
|
||||
"apps/ui/tests/e2e/operation-lifecycle.spec.js"
|
||||
],
|
||||
"manual": [
|
||||
"docs/manual-regression-checklist.md"
|
||||
]
|
||||
},
|
||||
"id": "ui-operation-lifecycle",
|
||||
"owner": "operations-ui-community",
|
||||
"requirements": ["FR-1", "FR-46"],
|
||||
"requirements": [
|
||||
"FR-1",
|
||||
"FR-46"
|
||||
],
|
||||
"status": "blocked",
|
||||
"type": "ui",
|
||||
"user_outcome": "An administrator can create, edit, publish, archive, and inspect REST Operations."
|
||||
},
|
||||
{
|
||||
"capabilities": ["tools"],
|
||||
"evidence": {"automated": ["apps/ui/tests/e2e/wizard.spec.js"], "manual": ["docs/capability-baseline/manual-checklist.md"]},
|
||||
"capabilities": [
|
||||
"tools"
|
||||
],
|
||||
"evidence": {
|
||||
"automated": [
|
||||
"apps/ui/tests/e2e/wizard.spec.js"
|
||||
],
|
||||
"manual": [
|
||||
"docs/capability-baseline/manual-checklist.md"
|
||||
]
|
||||
},
|
||||
"id": "ui-operation-test",
|
||||
"owner": "runtime-ui-community",
|
||||
"requirements": ["FR-2", "FR-46"],
|
||||
"requirements": [
|
||||
"FR-2",
|
||||
"FR-46"
|
||||
],
|
||||
"status": "implemented",
|
||||
"type": "ui",
|
||||
"user_outcome": "An administrator can execute a bounded Operation test and inspect its safe outcome."
|
||||
},
|
||||
{
|
||||
"capabilities": ["tools"],
|
||||
"evidence": {"automated": ["apps/ui/tests/e2e/operations.spec.js", "apps/ui/tests/e2e/wizard.spec.js"], "manual": ["docs/capability-baseline/manual-checklist.md"]},
|
||||
"capabilities": [
|
||||
"tools"
|
||||
],
|
||||
"evidence": {
|
||||
"automated": [
|
||||
"apps/ui/tests/e2e/operation-lifecycle.spec.js",
|
||||
"apps/ui/tests/e2e/wizard.spec.js"
|
||||
],
|
||||
"manual": [
|
||||
"docs/manual-regression-checklist.md"
|
||||
]
|
||||
},
|
||||
"id": "ui-operation-import-export",
|
||||
"owner": "import-ui-community",
|
||||
"requirements": ["FR-1", "FR-46"],
|
||||
"requirements": [
|
||||
"FR-1",
|
||||
"FR-46"
|
||||
],
|
||||
"status": "implemented",
|
||||
"type": "ui",
|
||||
"user_outcome": "An administrator can import OpenAPI or YAML drafts and export an Operation configuration."
|
||||
},
|
||||
{
|
||||
"capabilities": ["tools"],
|
||||
"evidence": {"automated": ["apps/ui/tests/e2e/agents.spec.js"], "manual": ["docs/capability-baseline/manual-checklist.md"]},
|
||||
"capabilities": [
|
||||
"tools"
|
||||
],
|
||||
"evidence": {
|
||||
"automated": [
|
||||
"apps/ui/tests/e2e/agents.spec.js"
|
||||
],
|
||||
"manual": [
|
||||
"docs/capability-baseline/manual-checklist.md"
|
||||
]
|
||||
},
|
||||
"id": "ui-agent-management",
|
||||
"notes": "Story 1.13 added catalog_revision display, serialized lifecycle controls and stale-conflict recovery evidence, but full Agent lifecycle UX remains blocked until production-grade Draft/Published editing flows are manually qualified.",
|
||||
"owner": "agents-ui-community",
|
||||
"requirements": ["FR-4", "FR-46"],
|
||||
"requirements": [
|
||||
"FR-4",
|
||||
"FR-46"
|
||||
],
|
||||
"status": "blocked",
|
||||
"type": "ui",
|
||||
"user_outcome": "An administrator can manage an Agent and its published Operation bindings."
|
||||
},
|
||||
{
|
||||
"capabilities": ["tools"],
|
||||
"evidence": {"automated": ["apps/ui/tests/e2e/api-keys.spec.js"], "manual": ["docs/capability-baseline/manual-checklist.md"]},
|
||||
"capabilities": [
|
||||
"tools"
|
||||
],
|
||||
"evidence": {
|
||||
"automated": [
|
||||
"apps/ui/tests/e2e/api-keys.spec.js",
|
||||
"apps/admin-api/tests/integration/credential_lifecycle.rs"
|
||||
],
|
||||
"manual": [
|
||||
"docs/capability-baseline/manual-checklist.md"
|
||||
]
|
||||
},
|
||||
"id": "ui-mcp-approval-keys",
|
||||
"owner": "keys-ui-community",
|
||||
"requirements": ["FR-3", "FR-5", "FR-46"],
|
||||
"requirements": [
|
||||
"FR-3",
|
||||
"FR-5",
|
||||
"FR-46"
|
||||
],
|
||||
"status": "implemented",
|
||||
"type": "ui",
|
||||
"user_outcome": "An administrator can create, reveal once, inspect, revoke, and delete MCP or approval keys."
|
||||
"user_outcome": "An administrator can create, reveal once, inspect, revoke, and terminal-soft-delete MCP or approval keys without retaining raw key material in the UI."
|
||||
},
|
||||
{
|
||||
"capabilities": ["tools"],
|
||||
"evidence": {"automated": ["apps/ui/tests/e2e/secrets.spec.js", "apps/ui/tests/e2e/wizard.spec.js"], "manual": ["docs/capability-baseline/manual-checklist.md"]},
|
||||
"capabilities": [
|
||||
"tools"
|
||||
],
|
||||
"evidence": {
|
||||
"automated": [
|
||||
"apps/ui/tests/e2e/secrets.spec.js",
|
||||
"apps/ui/tests/e2e/wizard.spec.js"
|
||||
],
|
||||
"manual": [
|
||||
"docs/capability-baseline/manual-checklist.md"
|
||||
]
|
||||
},
|
||||
"id": "ui-auth-profiles-upstreams-secrets",
|
||||
"owner": "credentials-ui-community",
|
||||
"requirements": ["FR-3", "FR-46"],
|
||||
"requirements": [
|
||||
"FR-3",
|
||||
"FR-46"
|
||||
],
|
||||
"status": "blocked",
|
||||
"type": "ui",
|
||||
"user_outcome": "An administrator can configure reusable upstream, Auth Profile, and secret references without re-reading plaintext."
|
||||
},
|
||||
{
|
||||
"capabilities": ["tools"],
|
||||
"evidence": {"automated": ["apps/ui/tests/e2e/logs-usage.spec.js"], "manual": ["docs/capability-baseline/manual-checklist.md"]},
|
||||
"capabilities": [
|
||||
"tools"
|
||||
],
|
||||
"evidence": {
|
||||
"automated": [
|
||||
"apps/ui/tests/e2e/logs-usage.spec.js"
|
||||
],
|
||||
"manual": [
|
||||
"docs/capability-baseline/manual-checklist.md"
|
||||
]
|
||||
},
|
||||
"id": "ui-logs-approvals",
|
||||
"owner": "history-ui-community",
|
||||
"requirements": ["FR-5", "FR-6", "FR-46"],
|
||||
"requirements": [
|
||||
"FR-5",
|
||||
"FR-6",
|
||||
"FR-46"
|
||||
],
|
||||
"status": "blocked",
|
||||
"type": "ui",
|
||||
"user_outcome": "An administrator can inspect invocation logs, safe details, and pending approvals."
|
||||
"user_outcome": "An administrator can inspect invocation logs, safe request/trace details, filter by outcome, export CSV, and inspect pending approvals."
|
||||
},
|
||||
{
|
||||
"capabilities": ["tools"],
|
||||
"evidence": {"automated": ["apps/ui/tests/e2e/logs-usage.spec.js"], "manual": ["docs/capability-baseline/manual-checklist.md"]},
|
||||
"capabilities": [
|
||||
"tools"
|
||||
],
|
||||
"evidence": {
|
||||
"automated": [
|
||||
"apps/ui/tests/e2e/logs-usage.spec.js"
|
||||
],
|
||||
"manual": [
|
||||
"docs/capability-baseline/manual-checklist.md"
|
||||
]
|
||||
},
|
||||
"id": "ui-usage",
|
||||
"owner": "usage-ui-community",
|
||||
"requirements": ["FR-6", "FR-46"],
|
||||
"requirements": [
|
||||
"FR-6",
|
||||
"FR-46"
|
||||
],
|
||||
"status": "blocked",
|
||||
"type": "ui",
|
||||
"user_outcome": "An administrator can inspect workspace, Operation, and Agent usage summaries."
|
||||
"user_outcome": "An administrator can inspect workspace, Operation, Agent, and safe outcome-group usage summaries."
|
||||
},
|
||||
{
|
||||
"capabilities": ["tools"],
|
||||
"evidence": {"automated": ["apps/admin-api/tests/integration/auth_rate_limit.rs", "apps/admin-api/tests/integration/community_access_usage.rs"], "manual": ["docs/capability-baseline/manual-checklist.md"]},
|
||||
"capabilities": [
|
||||
"tools"
|
||||
],
|
||||
"evidence": {
|
||||
"automated": [
|
||||
"apps/admin-api/tests/integration/auth_rate_limit.rs",
|
||||
"apps/admin-api/tests/integration/community_access_usage.rs"
|
||||
],
|
||||
"manual": [
|
||||
"docs/capability-baseline/manual-checklist.md"
|
||||
]
|
||||
},
|
||||
"id": "api-auth-workspace",
|
||||
"owner": "admin-api-community",
|
||||
"requirements": ["FR-3", "FR-46"],
|
||||
"requirements": [
|
||||
"FR-3",
|
||||
"FR-46"
|
||||
],
|
||||
"status": "implemented",
|
||||
"type": "api",
|
||||
"user_outcome": "Authenticated Admin API requests are scoped to the authorized Community workspace."
|
||||
},
|
||||
{
|
||||
"capabilities": ["tools"],
|
||||
"evidence": {"automated": ["apps/admin-api/tests/integration/operations_agents.rs"], "manual": ["docs/capability-baseline/manual-checklist.md"]},
|
||||
"capabilities": [
|
||||
"tools"
|
||||
],
|
||||
"evidence": {
|
||||
"automated": [
|
||||
"apps/admin-api/tests/integration/operation_lifecycle.rs",
|
||||
"crates/crank-registry/tests/integration/operations_artifacts.rs"
|
||||
],
|
||||
"manual": [
|
||||
"docs/admin-api.md"
|
||||
]
|
||||
},
|
||||
"id": "api-operation-lifecycle",
|
||||
"owner": "operations-api-community",
|
||||
"requirements": ["FR-1", "FR-46"],
|
||||
"requirements": [
|
||||
"FR-1",
|
||||
"FR-46"
|
||||
],
|
||||
"status": "implemented",
|
||||
"type": "api",
|
||||
"user_outcome": "Admin API clients can manage immutable published Operation versions and drafts."
|
||||
},
|
||||
{
|
||||
"capabilities": ["tools"],
|
||||
"evidence": {"automated": ["apps/admin-api/tests/integration/operations_agents.rs"], "manual": ["docs/capability-baseline/manual-checklist.md"]},
|
||||
"capabilities": [
|
||||
"tools"
|
||||
],
|
||||
"evidence": {
|
||||
"automated": [
|
||||
"apps/admin-api/tests/integration/operations_agents.rs",
|
||||
"crates/crank-adapter-rest/tests/integration/client.rs",
|
||||
"crates/crank-adapter-rest/tests/integration/outbound_security.rs",
|
||||
"crates/crank-runtime/tests/stages.rs"
|
||||
],
|
||||
"manual": [
|
||||
"docs/capability-baseline/manual-checklist.md"
|
||||
]
|
||||
},
|
||||
"id": "api-operation-test-run",
|
||||
"owner": "runtime-api-community",
|
||||
"requirements": ["FR-2", "FR-46"],
|
||||
"requirements": [
|
||||
"FR-2",
|
||||
"FR-46"
|
||||
],
|
||||
"status": "implemented",
|
||||
"type": "api",
|
||||
"user_outcome": "Admin API clients can execute a REST Operation test through the current runtime boundary."
|
||||
},
|
||||
{
|
||||
"capabilities": ["tools"],
|
||||
"evidence": {"automated": ["apps/admin-api/tests/integration/openapi_import.rs", "apps/admin-api/tests/integration/secrets_import_auth.rs"], "manual": ["docs/capability-baseline/manual-checklist.md"]},
|
||||
"capabilities": [
|
||||
"tools"
|
||||
],
|
||||
"evidence": {
|
||||
"automated": [
|
||||
"apps/admin-api/tests/integration/operation_lifecycle.rs",
|
||||
"apps/admin-api/tests/integration/secrets_import_auth.rs"
|
||||
],
|
||||
"manual": [
|
||||
"docs/admin-api.md"
|
||||
]
|
||||
},
|
||||
"id": "api-operation-import-export",
|
||||
"owner": "import-api-community",
|
||||
"requirements": ["FR-1", "FR-46"],
|
||||
"requirements": [
|
||||
"FR-1",
|
||||
"FR-46"
|
||||
],
|
||||
"status": "implemented",
|
||||
"type": "api",
|
||||
"user_outcome": "Admin API clients can preview and create OpenAPI imports and round-trip YAML configuration."
|
||||
},
|
||||
{
|
||||
"capabilities": ["tools"],
|
||||
"evidence": {"automated": ["apps/admin-api/tests/integration/operations_agents.rs"], "manual": ["docs/capability-baseline/manual-checklist.md"]},
|
||||
"capabilities": [
|
||||
"tools"
|
||||
],
|
||||
"evidence": {
|
||||
"automated": [
|
||||
"apps/admin-api/tests/integration/agent_catalog.rs",
|
||||
"apps/admin-api/tests/integration/operations_agents.rs",
|
||||
"crates/crank-registry/tests/integration/agents_usage.rs"
|
||||
],
|
||||
"manual": [
|
||||
"docs/admin-api.md"
|
||||
]
|
||||
},
|
||||
"id": "api-agent-catalog",
|
||||
"owner": "agents-api-community",
|
||||
"requirements": ["FR-4", "FR-46"],
|
||||
"requirements": [
|
||||
"FR-4",
|
||||
"FR-46"
|
||||
],
|
||||
"status": "implemented",
|
||||
"type": "api",
|
||||
"user_outcome": "Admin API clients can manage Agent revisions, exact bindings, publication, and catalog search policy."
|
||||
"user_outcome": "Admin API clients can manage revision-preconditioned Agent drafts, exact Published Operation bindings, immutable Published Agent Versions, publication, and catalog search policy."
|
||||
},
|
||||
{
|
||||
"capabilities": ["tools"],
|
||||
"evidence": {"automated": ["apps/admin-api/tests/integration/community_access_usage.rs"], "manual": ["docs/capability-baseline/manual-checklist.md"]},
|
||||
"capabilities": [
|
||||
"tools"
|
||||
],
|
||||
"evidence": {
|
||||
"automated": [
|
||||
"apps/admin-api/tests/integration/community_access_usage.rs",
|
||||
"apps/admin-api/tests/integration/credential_lifecycle.rs",
|
||||
"apps/mcp-server/tests/integration/catalog_access.rs"
|
||||
],
|
||||
"manual": [
|
||||
"docs/capability-baseline/manual-checklist.md"
|
||||
]
|
||||
},
|
||||
"id": "api-mcp-approval-keys",
|
||||
"owner": "keys-api-community",
|
||||
"requirements": ["FR-3", "FR-5", "FR-46"],
|
||||
"requirements": [
|
||||
"FR-3",
|
||||
"FR-5",
|
||||
"FR-46"
|
||||
],
|
||||
"status": "implemented",
|
||||
"type": "api",
|
||||
"user_outcome": "Admin API clients can manage separately scoped MCP and approval keys with a single raw disclosure."
|
||||
"user_outcome": "Admin API clients can manage separately scoped MCP and approval keys with a single raw disclosure, origin enforcement, immediate revocation, terminal soft-delete, and safe audit evidence."
|
||||
},
|
||||
{
|
||||
"capabilities": ["tools"],
|
||||
"evidence": {"automated": ["apps/admin-api/tests/integration/secrets_import_auth.rs"], "manual": ["docs/capability-baseline/manual-checklist.md"]},
|
||||
"capabilities": [
|
||||
"tools"
|
||||
],
|
||||
"evidence": {
|
||||
"automated": [
|
||||
"apps/admin-api/tests/integration/secrets_import_auth.rs",
|
||||
"apps/admin-api/tests/integration/credential_lifecycle.rs"
|
||||
],
|
||||
"manual": [
|
||||
"docs/capability-baseline/manual-checklist.md"
|
||||
]
|
||||
},
|
||||
"id": "api-auth-profiles-upstreams-secrets",
|
||||
"owner": "credentials-api-community",
|
||||
"requirements": ["FR-3", "FR-46"],
|
||||
"requirements": [
|
||||
"FR-3",
|
||||
"FR-46"
|
||||
],
|
||||
"status": "implemented",
|
||||
"type": "api",
|
||||
"user_outcome": "Admin API clients can manage upstreams, Auth Profiles, and encrypted secret references."
|
||||
"user_outcome": "Admin API clients can manage upstreams, Auth Profiles, encrypted secret references, atomic Secret rotation, and safe credential audit evidence."
|
||||
},
|
||||
{
|
||||
"capabilities": ["tools"],
|
||||
"evidence": {"automated": ["apps/admin-api/tests/integration/community_access_usage.rs"], "manual": ["docs/capability-baseline/manual-checklist.md"]},
|
||||
"capabilities": [
|
||||
"tools"
|
||||
],
|
||||
"evidence": {
|
||||
"automated": [
|
||||
"crates/crank-runtime/src/secret_crypto.rs",
|
||||
"crates/crank-registry/tests/integration/master_key_identity.rs",
|
||||
"apps/admin-api/tests/migration_command.rs",
|
||||
"apps/admin-api/tests/config_startup.rs",
|
||||
"apps/mcp-server/tests/config_startup.rs"
|
||||
],
|
||||
"manual": [
|
||||
"docs/secrets-and-auth.md",
|
||||
"docs/migrations.md"
|
||||
]
|
||||
},
|
||||
"id": "api-master-key-identity-rotation",
|
||||
"owner": "credentials-api-community",
|
||||
"requirements": [
|
||||
"FR-3",
|
||||
"FR-46"
|
||||
],
|
||||
"status": "implemented",
|
||||
"type": "api",
|
||||
"user_outcome": "Operators can verify one active non-secret master-key identity across Admin and MCP processes and rotate the key through a durable preflight, resume, verify, promote or abort workflow."
|
||||
},
|
||||
{
|
||||
"capabilities": [
|
||||
"tools"
|
||||
],
|
||||
"evidence": {
|
||||
"automated": [
|
||||
"apps/admin-api/tests/integration/community_access_usage.rs"
|
||||
],
|
||||
"manual": [
|
||||
"docs/capability-baseline/manual-checklist.md"
|
||||
]
|
||||
},
|
||||
"id": "api-approvals",
|
||||
"owner": "approvals-api-community",
|
||||
"requirements": ["FR-5", "FR-46"],
|
||||
"requirements": [
|
||||
"FR-5",
|
||||
"FR-46"
|
||||
],
|
||||
"status": "implemented",
|
||||
"type": "api",
|
||||
"user_outcome": "Admin API clients can list and inspect approval requests without leaking unsafe payloads."
|
||||
},
|
||||
{
|
||||
"capabilities": ["tools"],
|
||||
"evidence": {"automated": ["apps/admin-api/tests/integration/community_access_usage.rs", "apps/admin-api/tests/dc08.rs"], "manual": ["docs/capability-baseline/manual-checklist.md"]},
|
||||
"capabilities": [
|
||||
"tools"
|
||||
],
|
||||
"evidence": {
|
||||
"automated": [
|
||||
"apps/admin-api/tests/integration/logs_usage.rs",
|
||||
"apps/admin-api/tests/integration/community_access_usage.rs",
|
||||
"apps/admin-api/tests/dc08.rs",
|
||||
"crates/crank-registry/tests/integration/agents_usage.rs"
|
||||
],
|
||||
"manual": [
|
||||
"docs/capability-baseline/manual-checklist.md"
|
||||
]
|
||||
},
|
||||
"id": "api-logs-usage",
|
||||
"owner": "history-api-community",
|
||||
"requirements": ["FR-6", "FR-46"],
|
||||
"requirements": [
|
||||
"FR-6",
|
||||
"FR-46"
|
||||
],
|
||||
"status": "implemented",
|
||||
"type": "api",
|
||||
"user_outcome": "Admin API clients can query safe invocation history and usage summaries."
|
||||
"user_outcome": "Admin API clients can query safe cursor-paginated invocation history, log details, CSV export, and usage summaries with UTC half-open periods and outcome groups."
|
||||
},
|
||||
{
|
||||
"capabilities": ["tools"],
|
||||
"evidence": {"automated": ["apps/mcp-server/tests/integration/transport_protocol.rs"], "manual": ["docs/capability-baseline/manual-checklist.md"]},
|
||||
"capabilities": [
|
||||
"tools"
|
||||
],
|
||||
"evidence": {
|
||||
"automated": [
|
||||
"apps/mcp-server/tests/integration/transport_protocol.rs"
|
||||
],
|
||||
"manual": [
|
||||
"docs/capability-baseline/manual-checklist.md"
|
||||
]
|
||||
},
|
||||
"id": "mcp-transport-session",
|
||||
"owner": "mcp-transport-community",
|
||||
"requirements": ["FR-3", "FR-4", "FR-46"],
|
||||
"requirements": [
|
||||
"FR-3",
|
||||
"FR-4",
|
||||
"FR-46"
|
||||
],
|
||||
"status": "implemented",
|
||||
"type": "mcp",
|
||||
"user_outcome": "An authorized MCP client can initialize and terminate a bounded Streamable HTTP session."
|
||||
},
|
||||
{
|
||||
"capabilities": ["tools"],
|
||||
"evidence": {"automated": ["apps/mcp-server/tests/integration/transport_protocol.rs", "apps/mcp-server/tests/integration/catalog_access.rs"], "manual": ["docs/capability-baseline/manual-checklist.md"]},
|
||||
"capabilities": [
|
||||
"tools"
|
||||
],
|
||||
"evidence": {
|
||||
"automated": [
|
||||
"apps/mcp-server/tests/integration/transport_protocol.rs",
|
||||
"apps/mcp-server/tests/integration/catalog_access.rs",
|
||||
"crates/crank-registry/tests/integration/agents_usage.rs"
|
||||
],
|
||||
"manual": [
|
||||
"docs/mcp-interface.md"
|
||||
]
|
||||
},
|
||||
"id": "mcp-published-tool-list",
|
||||
"owner": "mcp-catalog-community",
|
||||
"requirements": ["FR-4", "FR-46"],
|
||||
"requirements": [
|
||||
"FR-4",
|
||||
"FR-46"
|
||||
],
|
||||
"status": "implemented",
|
||||
"type": "mcp",
|
||||
"user_outcome": "An MCP client sees only bound, authorized, published Tool versions for its Agent."
|
||||
"user_outcome": "An MCP client sees only bound, authorized, immutable published Tool versions for its Agent."
|
||||
},
|
||||
{
|
||||
"capabilities": ["tools"],
|
||||
"evidence": {"automated": ["apps/mcp-server/tests/integration/transport_protocol.rs", "apps/mcp-server/tests/integration/execution_stages.rs"], "manual": ["docs/capability-baseline/manual-checklist.md"]},
|
||||
"capabilities": [
|
||||
"tools"
|
||||
],
|
||||
"evidence": {
|
||||
"automated": [
|
||||
"apps/mcp-server/tests/integration/transport_protocol.rs",
|
||||
"apps/mcp-server/tests/integration/execution_stages.rs",
|
||||
"crates/crank-adapter-rest/tests/integration/client.rs",
|
||||
"crates/crank-adapter-rest/tests/integration/outbound_security.rs",
|
||||
"crates/crank-runtime/tests/stages.rs"
|
||||
],
|
||||
"manual": [
|
||||
"docs/capability-baseline/manual-checklist.md"
|
||||
]
|
||||
},
|
||||
"id": "mcp-published-tool-call",
|
||||
"owner": "mcp-runtime-community",
|
||||
"requirements": ["FR-2", "FR-4", "FR-6", "FR-46"],
|
||||
"requirements": [
|
||||
"FR-2",
|
||||
"FR-4",
|
||||
"FR-6",
|
||||
"FR-46"
|
||||
],
|
||||
"status": "implemented",
|
||||
"type": "mcp",
|
||||
"user_outcome": "An MCP client can invoke an authorized published REST Tool and receive a safe structured outcome."
|
||||
},
|
||||
{
|
||||
"capabilities": ["tools"],
|
||||
"evidence": {"automated": ["apps/mcp-server/tests/integration/tool_search.rs"], "manual": ["docs/capability-baseline/manual-checklist.md"]},
|
||||
"capabilities": [
|
||||
"tools"
|
||||
],
|
||||
"evidence": {
|
||||
"automated": [
|
||||
"apps/mcp-server/tests/integration/tool_search.rs",
|
||||
"apps/mcp-server/tests/integration/catalog_access.rs"
|
||||
],
|
||||
"manual": [
|
||||
"docs/mcp-interface.md"
|
||||
]
|
||||
},
|
||||
"id": "mcp-scoped-tool-search",
|
||||
"owner": "mcp-catalog-community",
|
||||
"requirements": ["FR-4", "FR-46"],
|
||||
"requirements": [
|
||||
"FR-4",
|
||||
"FR-46"
|
||||
],
|
||||
"status": "implemented",
|
||||
"type": "mcp",
|
||||
"user_outcome": "An MCP client can use Agent-scoped search meta-tools without escaping its published catalog."
|
||||
"user_outcome": "An MCP client can use Agent-scoped search meta-tools without escaping its published catalog, and stale search results are rejected by catalog_revision."
|
||||
},
|
||||
{
|
||||
"capabilities": ["tools"],
|
||||
"evidence": {"automated": ["apps/mcp-server/tests/integration/catalog_access/approval_access.rs"], "manual": ["docs/capability-baseline/manual-checklist.md"]},
|
||||
"capabilities": [
|
||||
"tools"
|
||||
],
|
||||
"evidence": {
|
||||
"automated": [
|
||||
"apps/mcp-server/tests/integration/catalog_access/approval_access.rs",
|
||||
"crates/crank-registry/tests/integration/approval.rs",
|
||||
"crates/crank-registry/tests/integration/migrations.rs",
|
||||
"apps/ui/tests/e2e/approvals.spec.js"
|
||||
],
|
||||
"manual": [
|
||||
"docs/capability-baseline/manual-checklist.md"
|
||||
]
|
||||
},
|
||||
"id": "mcp-approval-lifecycle",
|
||||
"owner": "mcp-approvals-community",
|
||||
"requirements": ["FR-5", "FR-46"],
|
||||
"requirements": [
|
||||
"FR-5",
|
||||
"FR-46"
|
||||
],
|
||||
"status": "implemented",
|
||||
"type": "mcp",
|
||||
"user_outcome": "A separately authorized approval client can list and decide pending requests with bounded side effects."
|
||||
},
|
||||
{
|
||||
"capabilities": ["tools"],
|
||||
"evidence": {"automated": ["apps/admin-api/tests/integration/request_context.rs", "apps/mcp-server/tests/integration/request_context.rs", "apps/mcp-server/tests/integration/execution_stages.rs", "crates/crank-registry/tests/integration/migrations.rs"], "manual": ["docs/observability.md"]},
|
||||
"capabilities": [
|
||||
"tools"
|
||||
],
|
||||
"evidence": {
|
||||
"automated": [
|
||||
"apps/admin-api/tests/integration/request_context.rs",
|
||||
"apps/mcp-server/tests/integration/request_context.rs",
|
||||
"apps/mcp-server/tests/integration/execution_stages.rs",
|
||||
"crates/crank-registry/tests/integration/migrations.rs"
|
||||
],
|
||||
"manual": [
|
||||
"docs/observability.md"
|
||||
]
|
||||
},
|
||||
"id": "api-canonical-request-trace-identity",
|
||||
"owner": "observability-community",
|
||||
"requirements": ["FR-27", "FR-46"],
|
||||
"requirements": [
|
||||
"FR-27",
|
||||
"FR-46"
|
||||
],
|
||||
"status": "implemented",
|
||||
"type": "api",
|
||||
"user_outcome": "Admin and MCP requests expose separate safe Request and Trace identities that survive runtime execution and history persistence without requiring telemetry export."
|
||||
},
|
||||
{
|
||||
"capabilities": ["tools"],
|
||||
"evidence": {"automated": ["tests/unit/test_validate_capability_inventory.py"], "manual": ["docs/intro.md"]},
|
||||
"capabilities": [
|
||||
"tools"
|
||||
],
|
||||
"evidence": {
|
||||
"automated": [
|
||||
"tests/unit/test_validate_capability_inventory.py"
|
||||
],
|
||||
"manual": [
|
||||
"docs/intro.md"
|
||||
]
|
||||
},
|
||||
"id": "planned-production-foundation",
|
||||
"owner": "platform-community",
|
||||
"requirements": ["FR-7", "FR-8", "FR-9", "FR-10", "FR-11", "FR-12", "FR-13", "FR-14", "FR-28", "FR-29", "FR-30", "FR-31", "FR-33", "FR-34", "FR-35"],
|
||||
"requirements": [
|
||||
"FR-7",
|
||||
"FR-8",
|
||||
"FR-9",
|
||||
"FR-10",
|
||||
"FR-11",
|
||||
"FR-12",
|
||||
"FR-13",
|
||||
"FR-14",
|
||||
"FR-28",
|
||||
"FR-29",
|
||||
"FR-30",
|
||||
"FR-31",
|
||||
"FR-33",
|
||||
"FR-34",
|
||||
"FR-35"
|
||||
],
|
||||
"status": "planned",
|
||||
"type": "api",
|
||||
"user_outcome": "Community runtime, safety, correlation, and observability foundations reach their planned production contracts."
|
||||
},
|
||||
{
|
||||
"capabilities": ["resources"],
|
||||
"evidence": {"automated": ["tests/unit/test_validate_capability_inventory.py"], "manual": ["docs/intro.md"]},
|
||||
"capabilities": [
|
||||
"resources"
|
||||
],
|
||||
"evidence": {
|
||||
"automated": [
|
||||
"tests/unit/test_validate_capability_inventory.py"
|
||||
],
|
||||
"manual": [
|
||||
"docs/intro.md"
|
||||
]
|
||||
},
|
||||
"id": "planned-resource-read",
|
||||
"owner": "mcp-community",
|
||||
"requirements": ["FR-15", "FR-16", "FR-46"],
|
||||
"requirements": [
|
||||
"FR-15",
|
||||
"FR-16",
|
||||
"FR-46"
|
||||
],
|
||||
"status": "planned",
|
||||
"type": "mcp",
|
||||
"user_outcome": "MCP clients can discover and read published Resources."
|
||||
},
|
||||
{
|
||||
"capabilities": ["prompts"],
|
||||
"evidence": {"automated": ["tests/unit/test_validate_capability_inventory.py"], "manual": ["docs/intro.md"]},
|
||||
"capabilities": [
|
||||
"prompts"
|
||||
],
|
||||
"evidence": {
|
||||
"automated": [
|
||||
"tests/unit/test_validate_capability_inventory.py"
|
||||
],
|
||||
"manual": [
|
||||
"docs/intro.md"
|
||||
]
|
||||
},
|
||||
"id": "planned-prompt-get",
|
||||
"owner": "mcp-community",
|
||||
"requirements": ["FR-17", "FR-18", "FR-19", "FR-46"],
|
||||
"requirements": [
|
||||
"FR-17",
|
||||
"FR-18",
|
||||
"FR-19",
|
||||
"FR-46"
|
||||
],
|
||||
"status": "planned",
|
||||
"type": "mcp",
|
||||
"user_outcome": "MCP clients can list and render published parameterized Prompts."
|
||||
},
|
||||
{
|
||||
"capabilities": ["tasks"],
|
||||
"evidence": {"automated": ["tests/unit/test_validate_capability_inventory.py"], "manual": ["docs/intro.md"]},
|
||||
"capabilities": [
|
||||
"tasks"
|
||||
],
|
||||
"evidence": {
|
||||
"automated": [
|
||||
"tests/unit/test_validate_capability_inventory.py"
|
||||
],
|
||||
"manual": [
|
||||
"docs/intro.md"
|
||||
]
|
||||
},
|
||||
"id": "planned-background-task",
|
||||
"owner": "execution-community",
|
||||
"requirements": ["FR-20", "FR-21", "FR-22", "FR-23", "FR-24", "FR-25", "FR-26", "FR-46"],
|
||||
"requirements": [
|
||||
"FR-20",
|
||||
"FR-21",
|
||||
"FR-22",
|
||||
"FR-23",
|
||||
"FR-24",
|
||||
"FR-25",
|
||||
"FR-26",
|
||||
"FR-46"
|
||||
],
|
||||
"status": "planned",
|
||||
"type": "mcp",
|
||||
"user_outcome": "Users can observe and control durable background executions."
|
||||
},
|
||||
{
|
||||
"capabilities": ["load_runs"],
|
||||
"evidence": {"automated": ["tests/unit/test_validate_capability_inventory.py"], "manual": ["docs/intro.md"]},
|
||||
"capabilities": [
|
||||
"load_runs"
|
||||
],
|
||||
"evidence": {
|
||||
"automated": [
|
||||
"tests/unit/test_validate_capability_inventory.py"
|
||||
],
|
||||
"manual": [
|
||||
"docs/intro.md"
|
||||
]
|
||||
},
|
||||
"id": "planned-load-run",
|
||||
"owner": "quality-community",
|
||||
"requirements": ["FR-36", "FR-37", "FR-38", "FR-39", "FR-40", "FR-41", "FR-46"],
|
||||
"requirements": [
|
||||
"FR-36",
|
||||
"FR-37",
|
||||
"FR-38",
|
||||
"FR-39",
|
||||
"FR-40",
|
||||
"FR-41",
|
||||
"FR-46"
|
||||
],
|
||||
"status": "planned",
|
||||
"type": "ui",
|
||||
"user_outcome": "Administrators can run a bounded load scenario and inspect its quality statistics."
|
||||
},
|
||||
{
|
||||
"capabilities": ["tools"],
|
||||
"evidence": {"automated": ["tests/unit/test_validate_capability_inventory.py"], "manual": ["docs/intro.md"]},
|
||||
"capabilities": [
|
||||
"tools"
|
||||
],
|
||||
"evidence": {
|
||||
"automated": [
|
||||
"tests/unit/test_validate_capability_inventory.py"
|
||||
],
|
||||
"manual": [
|
||||
"docs/intro.md"
|
||||
]
|
||||
},
|
||||
"id": "planned-community-deployment",
|
||||
"owner": "release-community",
|
||||
"requirements": ["FR-42", "FR-43", "FR-44", "FR-45", "FR-46"],
|
||||
"requirements": [
|
||||
"FR-42",
|
||||
"FR-43",
|
||||
"FR-44",
|
||||
"FR-45",
|
||||
"FR-46"
|
||||
],
|
||||
"status": "planned",
|
||||
"type": "api",
|
||||
"user_outcome": "Operators can deploy, upgrade, restore, and operate a production Community installation."
|
||||
},
|
||||
{
|
||||
"capabilities": ["tools"],
|
||||
"evidence": {"automated": ["tests/unit/test_validate_capability_inventory.py"], "manual": ["docs/intro.md"]},
|
||||
"capabilities": [
|
||||
"tools"
|
||||
],
|
||||
"evidence": {
|
||||
"automated": [
|
||||
"tests/unit/test_validate_capability_inventory.py"
|
||||
],
|
||||
"manual": [
|
||||
"docs/intro.md"
|
||||
]
|
||||
},
|
||||
"id": "planned-release-qualification",
|
||||
"owner": "quality-community",
|
||||
"requirements": ["FR-47", "FR-48", "FR-49", "FR-50", "FR-51", "FR-52", "FR-53", "FR-54"],
|
||||
"requirements": [
|
||||
"FR-47",
|
||||
"FR-48",
|
||||
"FR-49",
|
||||
"FR-50",
|
||||
"FR-51",
|
||||
"FR-52",
|
||||
"FR-53",
|
||||
"FR-54"
|
||||
],
|
||||
"status": "planned",
|
||||
"type": "api",
|
||||
"user_outcome": "A release candidate is qualified by immutable, reproducible technical and manual evidence."
|
||||
},
|
||||
{
|
||||
"capabilities": [
|
||||
"tools"
|
||||
],
|
||||
"evidence": {
|
||||
"automated": [
|
||||
"apps/admin-api/tests/integration/onboarding.rs",
|
||||
"crates/crank-registry/tests/integration/onboarding.rs",
|
||||
"apps/mcp-server/tests/onboarding.rs"
|
||||
],
|
||||
"manual": [
|
||||
"docs/admin-api.md"
|
||||
]
|
||||
},
|
||||
"id": "api-onboarding-first-call",
|
||||
"notes": "Implementation is present in the uncommitted Story 1.16 worktree. This baseline deliberately remains blocked until a clean source revision and collector-produced automated reports exist; no result is inferred from source files.",
|
||||
"owner": "onboarding-api-community",
|
||||
"requirements": [
|
||||
"FR-1",
|
||||
"FR-4",
|
||||
"FR-33"
|
||||
],
|
||||
"status": "blocked",
|
||||
"type": "api",
|
||||
"user_outcome": "Authenticated Community administrators receive an authoritative workspace onboarding projection and safe exact first public MCP Tool-call evidence."
|
||||
},
|
||||
{
|
||||
"capabilities": [
|
||||
"tools"
|
||||
],
|
||||
"evidence": {
|
||||
"automated": [
|
||||
"apps/ui/tests/e2e/onboarding.spec.js"
|
||||
],
|
||||
"manual": [
|
||||
"docs/manual-regression-checklist.md"
|
||||
]
|
||||
},
|
||||
"id": "ui-onboarding-first-call",
|
||||
"notes": "Automated and five-participant manual evidence must be recorded against a clean revision. The manual protocol is explicitly not_run in this baseline and does not constitute a release usability pass.",
|
||||
"owner": "onboarding-ui-community",
|
||||
"requirements": [
|
||||
"FR-1",
|
||||
"FR-4",
|
||||
"FR-33"
|
||||
],
|
||||
"status": "blocked",
|
||||
"type": "ui",
|
||||
"user_outcome": "A new self-hosted administrator can resume the optional Getting Started journey from manual REST Operation to an authoritative public MCP Tool call without retaining raw key material."
|
||||
}
|
||||
],
|
||||
"product": "crank-community",
|
||||
|
||||
+108
-2
@@ -12,11 +12,14 @@ Workspace содержит:
|
||||
- auth profiles;
|
||||
- logs;
|
||||
- usage.
|
||||
- immutable local ProductEvents.
|
||||
|
||||
## Operation
|
||||
|
||||
Operation описывает один REST integration contract.
|
||||
|
||||
`Operation` хранит identity и availability (`active|archived`), а `OperationVersion` — самостоятельный immutable execution/export snapshot. Каждый изменённый save добавляет monotonic Draft revision; publish переводит только current Draft в Published и никогда не двигает publication pointer назад. Archive меняет только aggregate availability.
|
||||
|
||||
Основные поля:
|
||||
|
||||
- `id`
|
||||
@@ -36,6 +39,8 @@ Operation описывает один REST integration contract.
|
||||
- `created_at`
|
||||
- `updated_at`
|
||||
|
||||
Version-local snapshot также хранит name/display/category/protocol/security level и provenance. Версии, созданные до migration v4, честно помечены `legacy_observed`: это migration-time наблюдение, а не выдуманная историческая публикация.
|
||||
|
||||
## REST target
|
||||
|
||||
REST target содержит:
|
||||
@@ -65,8 +70,19 @@ Agent определяет MCP endpoint и набор published operations.
|
||||
- `display_name`
|
||||
- `description`
|
||||
- `status`
|
||||
- `current_draft_version`
|
||||
- `latest_published_version`
|
||||
- `catalog_revision`
|
||||
|
||||
Operation публикуется в agent через binding.
|
||||
Agent catalog lifecycle (`agent-catalog-lifecycle-v9`) разделяет mutable aggregate и immutable Published Agent Version:
|
||||
|
||||
- Draft/current version можно редактировать до publication;
|
||||
- Published Agent Version и его bindings являются append-only snapshot и не изменяются in place;
|
||||
- `published_agents` хранит current published pointer и `catalog_revision`;
|
||||
- `catalog_revision` монотонно увеличивается при publish/unpublish/archive и связывает MCP search result с последующей `call_tool`;
|
||||
- Agent binding всегда указывает exact Published Operation Version из того же workspace.
|
||||
|
||||
Archive меняет availability Agent aggregate и запрещает новые публикации/bindings через Admin API, но не переписывает уже опубликованные snapshots и historical invocation evidence.
|
||||
|
||||
## Secrets и auth profiles
|
||||
|
||||
@@ -80,6 +96,59 @@ Operation публикуется в agent через binding.
|
||||
- API key query parameter.
|
||||
|
||||
Plaintext secret не возвращается через API после создания.
|
||||
Rotation добавляет новую encrypted version и делает её текущей для следующего
|
||||
execution. Operation и Auth Profile продолжают хранить только ссылки; их не
|
||||
нужно переписывать при rotation.
|
||||
|
||||
Master-key identity — отдельный durable security contract. PostgreSQL хранит
|
||||
только non-secret fingerprint, active epoch и cipher contract. Каждая новая
|
||||
`secret_versions` row содержит `master_key_epoch`; legacy rows считаются epoch
|
||||
`1`. Во время operator-controlled master-key rotation target ciphertext
|
||||
записывается рядом с текущим ciphertext и не становится authoritative до
|
||||
verification/promotion. Promotion атомарно переводит active epoch и переносит
|
||||
target ciphertext в основной ciphertext. Abort до promotion оставляет текущий
|
||||
epoch активным и очищает staged target fields.
|
||||
|
||||
## MCP и approval keys
|
||||
|
||||
`PlatformApiKey` хранит только metadata, bounded prefix и hash. Raw key
|
||||
возвращается один раз в create response. Новые Invocation History records могут
|
||||
безопасно хранить typed exact `platform_api_key_id`, полученный из verified
|
||||
machine credential; сам credential и hash в history не записываются.
|
||||
|
||||
Ключи разделены по назначению:
|
||||
|
||||
- `mcp_client` — доступ MCP client к опубликованному Agent catalog;
|
||||
- `approval` — approval side-channel с отдельным scope и optional
|
||||
`allowed_origins`.
|
||||
|
||||
Lifecycle статусы:
|
||||
|
||||
- `active`;
|
||||
- `revoked`;
|
||||
- `deleted`.
|
||||
|
||||
`revoke` немедленно прекращает доступ. `delete` переводит revoked key в
|
||||
terminal metadata state `deleted`, сохраняя provenance. Credential lifecycle
|
||||
пишет bounded audit events через `AuditSink`; plaintext, raw key, ciphertext и
|
||||
hash в audit payload не попадают.
|
||||
|
||||
## Approval requests
|
||||
|
||||
`approval_requests` — PostgreSQL authority для human approval side-channel.
|
||||
Pending uniqueness задаётся full scope index:
|
||||
`workspace_id, agent_id, operation_id, operation_version, request_fingerprint`
|
||||
для `status = 'pending'`. Fingerprint считается по canonical JSON input без
|
||||
служебных Crank control fields, поэтому retry с новым confirmation token не
|
||||
создаёт новый pending request. `request_payload_json` хранит bounded safe summary,
|
||||
а не raw payload; secret-like ключи редактируются до записи.
|
||||
|
||||
Execution transitions выполняются conditional update:
|
||||
`pending → approved|denied|expired`, затем `approved → executing`, затем
|
||||
terminal `completed|failed`. Повторный terminal approve/deny возвращает текущую
|
||||
заявку и не запускает второй side effect. Если процесс прервался после dispatch,
|
||||
recovery не делает автоматический retry mutating operation и сохраняет
|
||||
`approval_execution_outcome_unknown`.
|
||||
|
||||
## Logs и usage
|
||||
|
||||
@@ -88,9 +157,46 @@ Invocation logs фиксируют:
|
||||
- workspace;
|
||||
- agent;
|
||||
- operation;
|
||||
- точную версию operation для новых выполнений;
|
||||
- request id;
|
||||
- trace id;
|
||||
- status;
|
||||
- latency;
|
||||
- structured error context.
|
||||
- закрытую стадию выполнения;
|
||||
- стабильный код ошибки, retryability и certainty результата.
|
||||
- nullable exact MCP key identity для новых scoped machine invocations.
|
||||
|
||||
Поля классификации nullable для строк, созданных до migration v5: исторические
|
||||
значения не восстанавливаются предположениями. Новые Admin Draft Test и MCP
|
||||
выполнения записывают точную версию и оба correlation ID независимо от
|
||||
telemetry sampling.
|
||||
|
||||
Usage rollups агрегируют вызовы по периодам.
|
||||
Агрегация использует UTC half-open windows `[start, end)` и не группирует по
|
||||
Request ID или Trace ID. Для операторского анализа Usage отдельно отдаёт
|
||||
outcome groups `success`, `upstream`, `client`, `schema`, `crank`, чтобы не
|
||||
смешивать ошибки внешнего сервиса, входных данных, схемы и самого Crank.
|
||||
|
||||
Retention применим только к строкам `invocation_logs` старше effective cutoff.
|
||||
Effective cutoff вычисляется как минимум из requested cutoff и preservation
|
||||
floor, который сохраняет последние 90 дней usage-данных для операторской
|
||||
аналитики. Outcome retention операции typed и observable: `noop` или
|
||||
`completed`, количество удалённых rows и применённая policy. Retention не
|
||||
изменяет Published Operation Version, Agent snapshot, approvals или immutable
|
||||
release evidence.
|
||||
|
||||
## Local ProductEvents
|
||||
|
||||
`ProductEvent` — локальное immutable versioned событие продукта. В V11 закрытый
|
||||
словарь состоит из `onboarding_eligible`, `onboarding_started`,
|
||||
`onboarding_resumed`, `onboarding_dismissed`, `onboarding_abandoned` и
|
||||
`onboarding_completed`. Событие содержит workspace scope, `schema_version = 1`,
|
||||
UTC occurrence time и bounded idempotency key; `onboarding_eligible` обязательно
|
||||
несёт explicit `eligible_since`, поэтому denominator SM-11–SM-13 не выводится
|
||||
из browser state.
|
||||
|
||||
`product_events` append-only: update/delete отклоняются PostgreSQL trigger.
|
||||
Idempotency уникальна в пределах workspace. `product_event_daily_rollups`
|
||||
хранит только workspace/event/day counters (`events_total`, `eligible_total`),
|
||||
без raw user/object identifiers, payload, key material или usage. ProductEvents остаются
|
||||
локальными и не становятся внешней analytics отправкой без отдельного opt-in.
|
||||
|
||||
+51
-1
@@ -22,6 +22,8 @@
|
||||
- `auth_profiles`
|
||||
- `invocation_logs`
|
||||
- `usage_rollups`
|
||||
- `product_events`
|
||||
- `product_event_daily_rollups`
|
||||
- `yaml_import_jobs`
|
||||
|
||||
`memberships` используется как служебная привязка единственного администратора
|
||||
@@ -33,6 +35,7 @@
|
||||
|
||||
`operation_versions` хранит versioned JSON contract:
|
||||
|
||||
- version-local tool identity, display metadata, protocol и security level;
|
||||
- REST target;
|
||||
- schemas;
|
||||
- mappings;
|
||||
@@ -40,7 +43,22 @@
|
||||
- tool description;
|
||||
- samples metadata.
|
||||
|
||||
Migration v4 добавляет honest snapshot provenance и PostgreSQL trigger, который запрещает UPDATE/DELETE Published payload, включая parent cascade. Единственное разрешённое изменение version row — атомарный current Draft → Published transition; дальнейшие правки всегда INSERT новой revision.
|
||||
|
||||
Migration v5 расширяет `invocation_logs` nullable полями `operation_version`,
|
||||
`execution_stage`, `execution_error_code`, `retryability` и
|
||||
`outcome_certainty`. Новые execution записи заполняют их, legacy строки остаются
|
||||
неизвестными без fabricated backfill.
|
||||
|
||||
Migration v11 добавляет nullable `platform_api_key_id` и composite foreign key
|
||||
`(workspace_id, agent_id, platform_api_key_id)` к key того же Agent/workspace.
|
||||
Это exact credential provenance только для новых verified MCP invocations;
|
||||
legacy и external-verifier rows честно остаются `NULL`. Partial index по
|
||||
workspace/Agent/key/successful tool call поддерживает bounded onboarding
|
||||
projection без materialization history.
|
||||
|
||||
`published_operations` указывает на опубликованную version.
|
||||
Archive изменяет только `operations.status`; exact Agent bindings и invocation history продолжают ссылаться на immutable version.
|
||||
|
||||
## Agents
|
||||
|
||||
@@ -56,12 +74,44 @@
|
||||
|
||||
`secrets` хранит metadata.
|
||||
|
||||
`secret_versions` хранит encrypted value.
|
||||
`secret_versions` хранит encrypted value, `key_version` и
|
||||
`master_key_epoch`. Во время master-key rotation дополнительные nullable поля
|
||||
`target_ciphertext`, `target_key_version`, `target_master_key_epoch` содержат
|
||||
только verified candidate ciphertext до promotion; основной ciphertext остаётся
|
||||
рабочим до атомарного переключения epoch.
|
||||
|
||||
`master_key_identities` хранит non-secret identity активных/retired epochs:
|
||||
epoch, lowercase hex fingerprint, cipher contract, status и optional opaque
|
||||
`backup_ref`. Raw master key bytes и derived encryption keys в таблицах не
|
||||
хранятся.
|
||||
|
||||
`master_key_rotations` хранит durable operator workflow: source/target epoch,
|
||||
target fingerprint, state, checkpoint, counts и safe failure code. Только одна
|
||||
rotation может быть active (`running|verifying|verified`) для Secret writes.
|
||||
|
||||
`auth_profiles` хранит способ применения secrets к REST request.
|
||||
|
||||
`platform_api_keys` хранит metadata, bounded prefix и hash для MCP client и
|
||||
approval keys. Raw key в таблице не хранится. `status` принимает
|
||||
`active|revoked|deleted`; delete endpoint выполняет terminal soft-delete, чтобы
|
||||
не стирать audit/provenance. Для approval keys поле `allowed_origins_json`
|
||||
фиксирует точные разрешённые origins.
|
||||
|
||||
## Observability
|
||||
|
||||
`invocation_logs` хранит события runtime/MCP вызовов.
|
||||
|
||||
`usage_rollups` хранит агрегированную статистику.
|
||||
|
||||
## Local ProductEvents
|
||||
|
||||
`product_events` хранит workspace-scoped append-only ProductEvents: closed event
|
||||
name, `schema_version`, UTC `occurred_at`, bounded idempotency key и bounded
|
||||
JSON object properties. Unique `(workspace_id, idempotency_key)` делает replay
|
||||
безопасным; trigger отвергает `UPDATE` и `DELETE`.
|
||||
|
||||
`product_event_daily_rollups` хранит только daily counters по
|
||||
workspace/event/day. Его primary key не допускает дубликатов, а check constraint
|
||||
требует неотрицательные values и `eligible_total <= events_total`. Эти таблицы
|
||||
не содержат raw key, credentials, raw user/object identifiers, invocation payload
|
||||
или telemetry endpoint.
|
||||
|
||||
@@ -19,4 +19,28 @@ Published migrations are immutable and checksummed. Partial schema, unknown exte
|
||||
|
||||
Version 3 adds nullable canonical Trace ID storage and partial Request/Trace indexes. New application writes provide both identities; historical rows remain honestly nullable and are never assigned fabricated traces.
|
||||
|
||||
Version 4 freezes version-local Operation identity and provenance. Legacy versions are marked as migration-time observations rather than fabricated history. PostgreSQL rejects updates or cascade deletion of Published payloads; later edits append a new Draft revision.
|
||||
|
||||
Version 5 (`execution-outcome-v5`) adds nullable exact Operation version,
|
||||
execution stage, stable error code, retryability, and outcome certainty to
|
||||
Invocation History. Legacy v4 rows remain `NULL`; no classification is fabricated.
|
||||
|
||||
Version 7 (`master-key-identity-v7`) adds durable master-key identity and
|
||||
operator-controlled rotation state.
|
||||
|
||||
Version 8 (`admin-auth-lifecycle-v8`) adds one-time local bootstrap contracts, <!-- community-scope: allow=one-time-token -->
|
||||
CSRF-backed browser sessions, login backoff, and bounded admin auth audit.
|
||||
Production first-admin setup uses `crank-migrate admin-auth bootstrap-create`
|
||||
plus the `/login` bootstrap flow, not a static startup password.
|
||||
Lost Admin password recovery uses `crank-migrate admin-auth recover` with
|
||||
local secret files and active master-key identity verification; it revokes
|
||||
browser sessions and never prints password, pepper, master key, Secret plaintext,
|
||||
or ciphertext.
|
||||
|
||||
Version 11 (`onboarding-product-events-v11`) adds nullable exact MCP key
|
||||
provenance to Invocation History, immutable workspace-scoped local ProductEvents,
|
||||
and daily eligible-denominator rollups. Historical and externally verified rows
|
||||
remain honestly unscoped; no raw key, invocation payload, or external analytics
|
||||
export is introduced.
|
||||
|
||||
For the complete ledger inventory, recovery table, and authoring rules, see the canonical [Russian operator contract](../migrations.md).
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
# Единая граница выполнения REST Operation
|
||||
|
||||
Admin Draft Test и MCP `tools/call` используют один путь:
|
||||
|
||||
`RuntimeExecutionRequest → crank-runtime → ProtocolAdapter → crank-adapter-rest`.
|
||||
|
||||
До mapping и сетевого dispatch envelope фиксирует workspace, origin, точную
|
||||
Operation/version, Agent для immutable snapshot, authorization, resolved auth,
|
||||
correlation context и монотонный deadline. Runtime не создаёт fallback Request ID
|
||||
или Trace ID. `admin_draft` не принимает Agent ID, а `agent_snapshot` требует его.
|
||||
|
||||
## Результат и ошибки
|
||||
|
||||
Успешный результат — нормализованный JSON output. Ошибка один раз преобразуется
|
||||
в закрытый `ExecutionFailure`: `error_code`, `stage`, `retryability`,
|
||||
`outcome_certainty`, безопасные Request/Trace IDs и ограниченный typed context.
|
||||
Admin и MCP лишь проецируют этот descriptor в свои wire-ответы.
|
||||
|
||||
`retryability` принимает `never`, `safe`, `after_delay`, `manual_reconcile` или
|
||||
`requires_confirmation`. При timeout/transport cancellation после возможного
|
||||
dispatch возвращается `manual_reconcile + outcome_unknown`; автоматически
|
||||
повторять такой вызов нельзя. HTTP/MCP status и текст ошибки не меняют это решение.
|
||||
|
||||
Raw URL, upstream body/headers, parser/Reqwest/SQLx error и секреты не входят в
|
||||
ответ или Invocation History. Resolved auth и confirmation token имеют redacted
|
||||
`Debug` и не сериализуются как execution envelope.
|
||||
|
||||
## История и совместимость
|
||||
|
||||
Migration v5 (`execution-outcome-v5`) добавляет nullable поля exact Operation
|
||||
version, stage, stable error code, retryability и certainty. Для legacy v4 строк
|
||||
они остаются `NULL`; значения не фабрикуются. Новые Admin/MCP записи сохраняют
|
||||
их вместе с Request ID и Trace ID независимо от telemetry sampling.
|
||||
|
||||
Пути Admin API, MCP methods и успешные response fields не изменены. Поля ошибки
|
||||
добавлены обратно совместимо. Outbound DNS/redirect/retry/SSRF policy принадлежит
|
||||
Story 1.9 и этой границей не считается завершённой.
|
||||
@@ -77,7 +77,8 @@ cd apps/ui && npm run e2e
|
||||
- проверить search;
|
||||
- проверить protocol/category/agent filters;
|
||||
- открыть edit существующей operation;
|
||||
- удалить operation из demo workspace только если это безопасно для текущего стенда;
|
||||
- убедиться, что delete доступен только never-published Draft, а Published предлагает archive с подтверждением;
|
||||
- открыть одну Operation в двух вкладках, сохранить обе и проверить, что вторая получает localized stale recovery без потери local edits;
|
||||
- проверить success/error toasts;
|
||||
- проверить clean routes без `/html/...`.
|
||||
|
||||
@@ -96,7 +97,10 @@ cd apps/ui && npm run e2e
|
||||
- проверить sample upload;
|
||||
- проверить YAML export/import;
|
||||
- проверить `Test run`;
|
||||
- проверить `Publish`.
|
||||
- проверить, что Test run показывает безопасные Request ID и Trace ID с copy controls;
|
||||
- проверить `Publish`, затем edit: Published vN остаётся неизменной, новая правка становится Draft vN+1;
|
||||
- экспортировать Published vN как YAML v2, выполнить semantic no-op и changed upsert; previous Published Version не должна меняться;
|
||||
- архивировать Operation и проверить, что существующий published Agent по-прежнему list/call pinned vN, а новая binding отклоняется.
|
||||
|
||||
### 5.4. Agents
|
||||
|
||||
@@ -111,11 +115,20 @@ cd apps/ui && npm run e2e
|
||||
### 5.5. API Keys
|
||||
|
||||
- открыть `/api-keys`;
|
||||
- создать key;
|
||||
- создать MCP client key;
|
||||
- убедиться, что raw key показывается один раз;
|
||||
- проверить copy;
|
||||
- проверить revoke;
|
||||
- проверить delete;
|
||||
- закрыть reveal modal и убедиться, что raw key очищен из интерфейса;
|
||||
- проверить revoke и убедиться, что существующая MCP session теряет доступ без restart;
|
||||
- проверить delete revoked key: metadata остаётся со статусом `deleted`, raw/hash не отображаются;
|
||||
- создать approval key с `allowed_origins`;
|
||||
- проверить, что approval запрос с чужим `Origin` отклоняется.
|
||||
- создать Operation с human approval, вызвать Tool дважды с одинаковыми
|
||||
аргументами и разными control-token: должна остаться одна pending заявка;
|
||||
- убедиться, что pending approval summary показывает Agent/Operation Version,
|
||||
expiry и безопасные параметры без raw token/secret;
|
||||
- подтвердить заявку и повторить approve: второй запрос должен вернуть terminal
|
||||
status без повторного upstream side effect.
|
||||
- проверить `last_used_at` после реального machine-auth вызова при необходимости.
|
||||
|
||||
### 5.6. Secrets
|
||||
@@ -129,17 +142,23 @@ cd apps/ui && npm run e2e
|
||||
- `generic`;
|
||||
- убедиться, что plaintext не возвращается после create/rotate;
|
||||
- проверить usage references через auth profiles;
|
||||
- проверить, что удаление Secret, используемого Auth Profile, возвращает `409 secret_referenced_by_auth_profile`;
|
||||
- проверить, что rotation Secret меняет credential для следующего Wizard Test run без изменения Auth Profile/Operation refs;
|
||||
- проверить audit/log evidence: нет plaintext, ciphertext, hash или raw key;
|
||||
- проверить, что wizard quick-create синхронизируется с этой страницей.
|
||||
|
||||
### 5.7. Logs и Usage
|
||||
|
||||
- открыть `/logs`;
|
||||
- проверить loading/error/empty states;
|
||||
- проверить detail expansion;
|
||||
- проверить фильтры period, explicit UTC window, status, outcome group, level/search и кнопку Load more;
|
||||
- открыть detail expansion и убедиться, что видны Request ID, Trace ID и точная Operation Version, но нет raw секретов;
|
||||
- экспортировать CSV из Logs и проверить, что выбранные фильтры применились без page-limit обрезки;
|
||||
- открыть `/usage`;
|
||||
- проверить summary cards;
|
||||
- проверить timeline chart;
|
||||
- проверить CSV export.
|
||||
- проверить outcome groups `success`, `upstream`, `client`, `schema`, `crank`;
|
||||
- проверить server-side CSV export из `/usage/export.csv`, а не client-side snapshot браузера.
|
||||
|
||||
### 5.8. Workspace и Settings
|
||||
|
||||
@@ -152,6 +171,37 @@ cd apps/ui && npm run e2e
|
||||
- проверить profile update;
|
||||
- проверить password change.
|
||||
|
||||
### 5.9. Getting Started: протокол usability для пяти участников
|
||||
|
||||
Этот протокол является планом сбора ручного evidence, а не заявлением о
|
||||
пройденном исследовании. Выполнять его на пяти независимых участниках с fresh
|
||||
self-hosted installation и отдельным новым Admin account для каждого прогона.
|
||||
Участнику нельзя помогать документацией, исходным кодом, поиском по репозиторию
|
||||
или подсказками оператора; разрешён только сам продукт и нормальное MCP client
|
||||
окружение.
|
||||
|
||||
Для каждого участника:
|
||||
|
||||
- запустить таймер с первого authenticated рендера protected Admin UI;
|
||||
- использовать Getting Started, создать manual REST Operation, выполнить test,
|
||||
publish, создать/publish Agent и один MCP client key;
|
||||
- сохранить key только через one-time reveal <!-- community-scope: allow=one-time-token -->, выполнить публичные
|
||||
`initialize`, `notifications/initialized`, `tools/list`, `tools/call` по
|
||||
canonical endpoint;
|
||||
- остановить таймер после server-authoritative `first_call` с Agent/key/Tool,
|
||||
Request ID и Trace ID в snapshot/Invocation History;
|
||||
- проверить, что refresh/resume не раскрывает raw key, а revoke предлагает
|
||||
deliberate recovery без автоматического создания дубликата;
|
||||
- зафиксировать завершение, duration, все recovery/error states и факт
|
||||
eligibility отдельно от completion. Не записывать bearer, payload, URL
|
||||
частного контура или скриншоты с секретами.
|
||||
|
||||
Цель — median времени от первого authenticated render до authoritative first
|
||||
call не более 15 минут. Незавершивший участник цензурируется на 30-й минуте, но
|
||||
остаётся в conversion denominator; eligibility denominator и completion
|
||||
numerator публикуются раздельно. До фактического пятиучастникового запуска
|
||||
результат этого протокола остаётся `not_run`, а не `pass`.
|
||||
|
||||
## 6. Protocol smoke pass
|
||||
|
||||
Для smoke-проверки MCP использовать готовый public target из [public-smoke-targets.md](public-smoke-targets.md).
|
||||
|
||||
+43
-3
@@ -34,6 +34,13 @@ Authorization: Bearer <agent_api_key>
|
||||
|
||||
Для операций с подтверждением человеком нужен отдельный ключ подтверждения. Его тоже выдают в разделе **API ключи**, но в режиме **Подтверждения**. Такой ключ нельзя передавать LLM или MCP-клиенту. Он нужен только вашему внешнему интерфейсу, где пользователь нажимает «Подтвердить» или «Отклонить».
|
||||
|
||||
Ключи разных типов не взаимозаменяемы: `mcp_client` не принимается на approval
|
||||
endpoints, а `approval` не принимается для `initialize`, `tools/list` и
|
||||
`tools/call`. Revocation проверяется через PostgreSQL source of truth и начинает
|
||||
действовать без restart, включая уже существующие MCP session. Для approval keys
|
||||
может быть задан список `allowed_origins`; если HTTP `Origin` присутствует и не
|
||||
совпадает с разрешённым origin, запрос отклоняется до выполнения side effect.
|
||||
|
||||
## Поддерживаемые методы
|
||||
|
||||
MCP methods:
|
||||
@@ -76,6 +83,21 @@ MCP-Session-Id: <session_id>
|
||||
|
||||
Если клиент передает `MCP-Protocol-Version`, он должен совпадать с версией, согласованной при инициализации.
|
||||
|
||||
## Авторитетное evidence первого вызова
|
||||
|
||||
Getting Started засчитывает первый вызов только после полной публичной
|
||||
последовательности `initialize` → `notifications/initialized` → `tools/list` →
|
||||
успешный `tools/call` с тем же активным `mcp_client` key. Одного discovery,
|
||||
failed call, success через другой key или external verifier credential
|
||||
недостаточно. Invocation History сохраняет exact key ID вместе с Agent,
|
||||
immutable Operation Version, tool, UTC timestamp, Request ID и Trace ID.
|
||||
|
||||
Admin UI может безопасно сослаться на соответствующую history row и correlation
|
||||
IDs, но не показывает input arguments, payload, bearer value или upstream body.
|
||||
Если key отозван/deleted, Agent/Operation archive либо binding/revision больше
|
||||
не подтверждаются authoritative source of truth, onboarding возвращается в
|
||||
actionable state; старый raw key не восстанавливается.
|
||||
|
||||
## Пример `initialize`
|
||||
|
||||
```bash
|
||||
@@ -131,7 +153,9 @@ call_tool
|
||||
|
||||
`search_tools` принимает текст задачи, необязательные идентификаторы разделов и предел результатов. Ответ содержит полные входные схемы найденных инструментов и `catalog_revision`.
|
||||
|
||||
`call_tool` принимает имя найденного инструмента, его аргументы и полученную `catalog_revision`. Если за время между поиском и вызовом опубликована новая версия агента, вызов отклоняется с кодом `catalog_revision_changed`: клиент должен повторить поиск.
|
||||
`call_tool` принимает имя найденного инструмента, его аргументы и полученную `catalog_revision`. Если за время между поиском и вызовом опубликована новая версия агента, вызов отклоняется с кодом `agent_catalog_result_stale`: клиент должен повторить поиск.
|
||||
|
||||
`catalog_revision` берётся из immutable Published Agent catalog. Он не является user-controlled label и не заменяет Request/Trace ID; это bounded revision token для защиты пары `search_tools → call_tool` от stale results.
|
||||
|
||||
## Пример `tools/call`
|
||||
|
||||
@@ -161,6 +185,12 @@ Crank выполнит REST-запрос:
|
||||
GET https://api.frankfurter.dev/v1/latest?base=USD&symbols=EUR
|
||||
```
|
||||
|
||||
Ошибка `tools/call` сохраняет существующие JSON-RPC/`isError` semantics и
|
||||
добавляет в structured content поля `error_code`, `stage`, `retryability`,
|
||||
`outcome_certainty`, `request_id` и `trace_id`. Значение `manual_reconcile` вместе
|
||||
с `outcome_unknown` означает, что автоматический повтор небезопасен. Raw upstream
|
||||
body, URL, headers и внутренний текст ошибки не возвращаются.
|
||||
|
||||
## Операции с подтверждением человеком
|
||||
|
||||
Если в мастере операции включено **Подтверждение человеком**, первый `tools/call` не выполняет REST-запрос сразу. Вместо этого Crank создает ожидающий запрос на подтверждение и возвращает MCP-клиенту структурированный результат:
|
||||
@@ -185,6 +215,14 @@ GET https://api.frankfurter.dev/v1/latest?base=USD&symbols=EUR
|
||||
|
||||
`approval_url` в ответе является путем на MCP-сервере. Если вы публикуете MCP через префикс `/mcp`, внешний URL будет начинаться с `/mcp/v1/...`.
|
||||
|
||||
Approval identity считается по полному scope: workspace, Agent, immutable
|
||||
Operation Version и canonical JSON аргументы. Служебные поля Crank, например
|
||||
`_crank_confirmation_token`, не входят в fingerprint и не создают дубликаты.
|
||||
Активный pending request с тем же scope возвращается повторно. В metadata и
|
||||
`payload_preview` хранится только bounded safe summary: secret-like поля
|
||||
редактируются, raw approval key/auth headers/control tokens не сохраняются и не
|
||||
отдаются клиенту.
|
||||
|
||||
Внешний интерфейс подтверждения работает отдельным ключом подтверждения:
|
||||
|
||||
```bash
|
||||
@@ -217,8 +255,10 @@ curl https://crank.example.com/mcp/v1/default/sales/approvals/<approval_id>/appr
|
||||
идемпотентности: универсальный HTTP-клиент не может гарантировать ровно одно внешнее
|
||||
побочное действие при падении процесса между ответом upstream и записью результата.
|
||||
|
||||
Повторный `tools/call` с теми же агентом, операцией, версией и JSON-аргументами
|
||||
возвращает уже существующую активную заявку вместо создания дубликата.
|
||||
Повторный `tools/call` с теми же агентом, операцией, версией и canonical
|
||||
JSON-аргументами возвращает уже существующую активную заявку вместо создания
|
||||
дубликата. Повторный approve/deny terminal заявки возвращает текущий status и не
|
||||
запускает второй side effect.
|
||||
|
||||
Отклонение:
|
||||
|
||||
|
||||
+88
-3
@@ -16,12 +16,17 @@ docker compose -f deploy/community/docker-compose.yml --env-file deploy/communit
|
||||
2. Создайте и проверьте согласованный backup PostgreSQL и artifact storage. Для первой пустой установки зафиксируйте, что восстанавливать нечего.
|
||||
3. Проверьте immutable plan: `cargo run -p admin-api --bin crank-migrate -- plan --check` в source checkout либо `<compose> run --rm migrate crank-migrate plan` для образа.
|
||||
4. Примените sequence: `<compose> run --rm migrate crank-migrate apply`.
|
||||
5. Повторите preflight и убедитесь в `{"status":"current","version":3}`.
|
||||
5. Повторите preflight и убедитесь в `{"status":"current","version":11}`.
|
||||
6. Только теперь запускайте long-running services: `<compose> up -d`.
|
||||
|
||||
Обычный `up` также содержит обязательный migration job, но при upgrade он не заменяет предварительные preflight и backup. Migrator делает до десяти bounded попыток подключения с секундной паузой и затем безопасно завершается ошибкой.
|
||||
|
||||
Команда читает только `CRANK_DATABASE_URL`/`POSTGRES_*`. Master key, session secret, bootstrap password, MCP credentials и другие service secrets не входят в её config projection.
|
||||
Команда `plan|preflight|apply` читает только `CRANK_DATABASE_URL`/`POSTGRES_*`.
|
||||
Master key, session secret, MCP credentials и другие service secrets не входят
|
||||
в её migration config projection. Подкоманды `admin-auth bootstrap-create` и
|
||||
`admin-auth recover` также используют только database config. Bootstrap
|
||||
выводит одноразовый token; recovery читает новый пароль, password pepper и
|
||||
current master key только из локальных файлов.
|
||||
|
||||
## Фактический brownfield inventory
|
||||
|
||||
@@ -35,6 +40,26 @@ docker compose -f deploy/community/docker-compose.yml --env-file deploy/communit
|
||||
|
||||
Legacy extension row без зарегистрированного exact `(name, version, checksum)` несовместим. Community пока не публиковала extension migrations, поэтому authority не выдумывает им checksum и блокирует такие строки с `legacy_conflict`.
|
||||
|
||||
Version 4 (`operation-lifecycle-v4`) добавляет version-local Operation identity/provenance и DB immutability guard. Legacy rows получают `legacy_observed` с cutover timestamp; неизвестные historical actor/publication timestamps не фабрикуются. Published payload и parent cascade delete блокируются на уровне PostgreSQL.
|
||||
|
||||
Version 5 (`execution-outcome-v5`) добавляет nullable exact Operation version,
|
||||
execution stage, stable error code, retryability и outcome certainty в Invocation
|
||||
History. Legacy v4 строки остаются `NULL`; backfill не фабрикует классификацию.
|
||||
|
||||
Version 7 (`master-key-identity-v7`) добавляет durable master-key identity,
|
||||
rotation ledger и epoch-aware Secret ciphertext metadata. Это expand-изменение:
|
||||
legacy `secret_versions` получают default epoch `1`, а target ciphertext поля
|
||||
остаются `NULL` до operator-controlled rotation.
|
||||
|
||||
Version 8 (`admin-auth-lifecycle-v8`) добавляет local bootstrap contracts,
|
||||
CSRF hash для browser sessions, login backoff ledger и bounded admin security
|
||||
audit. Static startup password больше не является production bootstrap
|
||||
authority: первый администратор создаётся через `crank-migrate admin-auth
|
||||
bootstrap-create` и одноразовый token в UI. Потерянный Admin password
|
||||
восстанавливается локально через `crank-migrate admin-auth recover` с проверкой
|
||||
active master-key identity; команда не раскрывает старый пароль, Secrets или
|
||||
key material и отзывает browser sessions.
|
||||
|
||||
## Контракт sequence
|
||||
|
||||
Machine plan находится в [`schemas/migration-sequence.json`](schemas/migration-sequence.json) и проверяется командой:
|
||||
@@ -46,6 +71,20 @@ cargo run -p admin-api --bin crank-migrate -- plan --check
|
||||
- V1 — immutable brownfield baseline с историческим ledger token и отдельным exact-source SHA-256.
|
||||
- V2 — единый exact-byte expand SQL artifact, создающий canonical ledgers и MCP session schema; его SHA-256 закреплён в executable descriptor.
|
||||
- V3 — append-only expand для независимого nullable `invocation_logs.trace_id`, canonical-format constraint и partial request/trace indexes; исторические строки остаются `NULL` без fabricated backfill.
|
||||
- V4 — immutable Operation lifecycle и honest legacy snapshot provenance.
|
||||
- V5 — nullable typed execution outcome для N/N-1 чтения истории.
|
||||
- V7 — master-key identity/rotation foundation: non-secret active epoch,
|
||||
durable rotation state/checkpoint и target ciphertext metadata.
|
||||
- V8 — admin auth lifecycle foundation: one-time bootstrap contract, CSRF <!-- community-scope: allow=one-time-token -->
|
||||
session verifier, login backoff и bounded auth audit.
|
||||
- V9 — immutable Agent catalog lifecycle: catalog revision и DB guards для
|
||||
published Agent snapshots/bindings.
|
||||
- V10 — approval side-effect safety: workspace-scoped pending approval fingerprint
|
||||
index для full-scope deduplication.
|
||||
- V11 — onboarding ProductEvents и exact credential provenance: nullable scoped
|
||||
`invocation_logs.platform_api_key_id`, immutable local `product_events`, daily
|
||||
denominator rollups и partial success index. Legacy history не backfill-ится;
|
||||
external/unscoped credentials не получают fabricated key identity.
|
||||
- Каждая версия имеет contiguous `i64` version, стабильное имя, lowercase SHA-256, owner, phase, explicit readable schema min/max и backfill policy.
|
||||
- `migrate` требует bounded cursor/batch policy; `contract` дополнительно требует tracked compatibility evidence и закрытого окна.
|
||||
- Добавление descriptor без executable implementation блокируется `invalid_contract` до DB I/O.
|
||||
@@ -68,9 +107,55 @@ CLI/stderr возвращают bounded JSON: `code`, `stage`, nullable numeric
|
||||
| `apply_failed` | Transaction migration откатилась | Проверить matching artifact/backup, затем повторить preflight |
|
||||
| `storage_unavailable` | PostgreSQL/transport недоступен | Проверить сеть/TLS/права; секреты в diagnostic не копировать |
|
||||
| `config_invalid` | Database-only config невалиден | Исправить указанный config contract |
|
||||
| `invalid_command` | Неизвестная CLI команда/аргумент | Использовать только `plan`, `preflight`, `apply` |
|
||||
| `invalid_command` | Неизвестная CLI команда/аргумент | Использовать только `plan`, `preflight`, `apply`, `admin-auth bootstrap-create|recover` или `master-key status|preflight|rotate|verify|promote|abort` |
|
||||
| `contract_drift` | Committed machine plan расходится с Rust authority | Перегенерировать только для новой append-only version и проверить diff |
|
||||
| `invalid_contract` | Descriptor/implementation/window/evidence несовместимы | Исправить authoring contract до любого DB I/O |
|
||||
| `admin_recovery_rejected` | Recovery request не прошёл local/database checks | Проверить email, secret files и active master-key identity; не создавать второго Admin |
|
||||
| `master_key_identity_mismatch` | Local master key не совпадает с active PostgreSQL identity | Использовать правильный current/target key; не менять ciphertext вручную |
|
||||
| `master_key_rotation_in_progress` | Есть active rotation | Выполнить resume/verify/promote или abort |
|
||||
| `master_key_rotation_verification_failed` | Target ciphertext не прошёл проверку | Повторить rotate/verify или abort до promotion |
|
||||
|
||||
## Admin auth operator commands
|
||||
|
||||
Первичная и recovery-инициализация Admin identity выполняется только локальным
|
||||
operator command. Значения secret material не передаются через argv:
|
||||
|
||||
```bash
|
||||
crank-migrate admin-auth bootstrap-create --email owner@example.local
|
||||
|
||||
crank-migrate admin-auth recover \
|
||||
--email owner@example.local \
|
||||
--password-file /secure/new-admin-password.txt \
|
||||
--password-pepper-file /secure/password-pepper.txt \
|
||||
--master-key-file /secure/current-master.key
|
||||
```
|
||||
|
||||
`recover` проверяет active master-key identity в PostgreSQL, заменяет verifier
|
||||
существующего Admin account, отзывает все browser sessions/CSRF state и пишет
|
||||
bounded audit event. Команда не выводит password, pepper, master key, Secret
|
||||
plaintext или ciphertext.
|
||||
|
||||
## Master-key rotation operator commands
|
||||
|
||||
`crank-migrate master-key` использует тот же database-only config, что и
|
||||
миграции. Raw key material передаётся только через локальные файлы:
|
||||
|
||||
```bash
|
||||
crank-migrate master-key status
|
||||
crank-migrate master-key preflight --current-key-file /secure/current.key --target-key-file /secure/target.key --backup-ref offline-backup-ref
|
||||
crank-migrate master-key rotate --current-key-file /secure/current.key --target-key-file /secure/target.key --backup-ref offline-backup-ref
|
||||
crank-migrate master-key verify --target-key-file /secure/target.key
|
||||
crank-migrate master-key promote --target-key-file /secure/target.key
|
||||
crank-migrate master-key abort --rotation-id master-key-e1-to-e2
|
||||
```
|
||||
|
||||
`preflight` не меняет active epoch, ciphertext или rotation ledger. `rotate`
|
||||
можно повторять после interruption; команда пропускает уже staged rows и
|
||||
обновляет checkpoint/counts. `verify` расшифровывает staged target ciphertext
|
||||
target key. `promote` атомарно retired old active identity, registers target
|
||||
identity as active и переносит target ciphertext в основной ciphertext. До
|
||||
promotion текущий key остаётся рабочим; после promotion процессы нужно
|
||||
перезапустить с target key.
|
||||
|
||||
## Правила разработчика
|
||||
|
||||
|
||||
+38
-3
@@ -318,6 +318,17 @@ Crank сохраняет в PostgreSQL данные о тестовых запу
|
||||
опубликованных MCP-инструментов. Это отдельная прикладная история, а не
|
||||
копия stdout.
|
||||
|
||||
Новые записи сохраняют точную Operation version и закрытую классификацию
|
||||
`execution_stage`, `execution_error_code`, `retryability` и
|
||||
`outcome_certainty`. Admin Draft Test и MCP используют одну taxonomy; transport
|
||||
может отличаться, но не смысл результата. Legacy-записи до migration v5 имеют
|
||||
`NULL` в этих полях — Crank не выдумывает исторические исходы.
|
||||
|
||||
`outcome_unknown` означает, что внешний side effect мог произойти до timeout
|
||||
или transport failure. Такой исход требует ручной сверки и никогда не является
|
||||
сигналом для автоматического повтора. Request ID и Trace ID остаются полями
|
||||
корреляции и не используются как Prometheus labels.
|
||||
|
||||
Если внешнее действие завершилось, но PostgreSQL отклонил запись истории,
|
||||
Crank не меняет фактический результат и не повторяет действие. Вместо
|
||||
синтетической записи создаётся эксплуатационный инцидент `DC-08`:
|
||||
@@ -337,12 +348,25 @@ Crank не меняет фактический результат и не пов
|
||||
|
||||
- операция;
|
||||
- агент, если вызов пришел через MCP;
|
||||
- request id;
|
||||
- Request ID и Trace ID;
|
||||
- статус;
|
||||
- HTTP status code внешнего API;
|
||||
- время выполнения;
|
||||
- краткий preview запроса и ответа;
|
||||
- категория ошибки, если вызов завершился ошибкой.
|
||||
- execution stage, error code, retryability и certainty, если вызов завершился
|
||||
ошибкой.
|
||||
|
||||
Previews проходят единый redaction/size limit до записи в PostgreSQL, Admin API
|
||||
и CSV. Raw credentials, arbitrary headers/body и неограниченный текст ошибки не
|
||||
являются частью Invocation History. Legacy-записи могут иметь пустые typed
|
||||
execution fields; новые записи должны содержать Request ID и Trace ID.
|
||||
|
||||
Admin Logs API поддерживает workspace-scoped фильтры `period`, `level`,
|
||||
`status`, `source`, `operation_id`, `agent_id`, `search` и deterministic opaque
|
||||
cursor pagination. Detail view показывает те же безопасные поля и не раскрывает
|
||||
foreign-scope metadata через counts или разные формы ошибок. CSV export
|
||||
использует те же filters/auth/redaction и дополнительно экранирует
|
||||
spreadsheet-formula cells.
|
||||
|
||||
При обновлении опубликованного MCP-каталога событие `mcp.catalog.analyzed`
|
||||
содержит `tool_count`, `serialized_bytes`, `estimated_context_tokens`,
|
||||
@@ -359,7 +383,18 @@ Crank не меняет фактический результат и не пов
|
||||
- успешные и ошибочные вызовы;
|
||||
- долю ошибок;
|
||||
- задержки p50, p95 и p99;
|
||||
- распределение вызовов по операциям.
|
||||
- распределение вызовов по операциям;
|
||||
- распределение по Agent;
|
||||
- outcome groups `success`, `upstream`, `client`, `schema`, `crank`.
|
||||
|
||||
Периоды считаются в UTC как half-open interval `[start, end)`, поэтому запись
|
||||
на правой границе периода не дублируется в соседнем окне. Retention удаляет
|
||||
только старые Invocation History rows по typed observable outcome. Cleanup
|
||||
сохраняет публичный usage horizon последних 90 дней даже при более агрессивном
|
||||
requested cutoff и возвращает typed policy/outcome (`requested_cutoff`,
|
||||
`effective_cutoff`, deleted count, preserved window). Published Version audit,
|
||||
Agent snapshots и immutable release evidence не являются объектами retention
|
||||
cleanup.
|
||||
|
||||
## Для чего это нужно
|
||||
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
# Operation lifecycle contract
|
||||
|
||||
This contract is the executable oracle for Community Operation lifecycle.
|
||||
|
||||
## State and revision model
|
||||
|
||||
- Aggregate availability is `active` or `archived`.
|
||||
- A version is `draft` or `published`; legacy `testing` remains read-compatible only.
|
||||
- Every changed save appends exactly one monotonic version. Existing version payloads are never updated.
|
||||
- PostgreSQL rejects direct Published payload mutation and direct deletion/rewind of publication pointers.
|
||||
- New Agent bindings lock referenced Operation rows and reject archived aggregates; existing published Agent snapshots remain executable.
|
||||
- Publishing is limited to the current active Draft. Retrying the already published current version is idempotent; an older pointer is never restored.
|
||||
- Editing published content appends the next Draft. An Agent keeps its exact pinned Operation version until an explicit Agent lifecycle change.
|
||||
- Archiving changes aggregate availability only. Existing versions, published pointers, Agent snapshots and Invocation History remain unchanged.
|
||||
- Hard delete is limited to an active, never-published Draft without durable references.
|
||||
|
||||
## Conditional mutation contract
|
||||
|
||||
Aggregate reads return a strong state `ETag` bound to workspace, Operation identity, current version, availability, latest published version and representation version. Mutations of an existing Operation require that token in `If-Match`. Missing and stale preconditions return deterministic 428 and 409 outcomes. Exact version reads use a separate content-stable ETag.
|
||||
|
||||
## Portable YAML
|
||||
|
||||
Canonical export uses `format_version: "2"`. Legacy version 1 is import-only and is normalized into v2. The body limit is 256 KiB; one document, depth 64, at most 20,000 nodes, 4,096 collection items and 64 KiB line/scalar values are accepted. Credentials, URL userinfo, storage paths and persistence/runtime-only metadata are never portable.
|
||||
|
||||
## Stable failure codes
|
||||
|
||||
- `operation_precondition_required`
|
||||
- `operation_stale_version`
|
||||
- `operation_invalid_transition`
|
||||
- `operation_archived`
|
||||
- `operation_publish_blocked`
|
||||
- `operation_delete_forbidden`
|
||||
- `operation_yaml_too_large`
|
||||
- `operation_yaml_invalid`
|
||||
- `operation_yaml_unsupported`
|
||||
- `operation_yaml_conflict`
|
||||
@@ -4,10 +4,15 @@
|
||||
|
||||
## Перед запуском
|
||||
|
||||
- Сгенерирован сильный `CRANK_MASTER_KEY`.
|
||||
- Сгенерирован сильный `CRANK_MASTER_KEY` длиной не менее 32 bytes.
|
||||
- Зафиксирована процедура хранения и восстановления `CRANK_MASTER_KEY` вне
|
||||
product backup set; есть защищённый opaque `backup_ref` для rotation.
|
||||
- Сгенерирован сильный `CRANK_SESSION_SECRET`.
|
||||
- Сгенерирован сильный `CRANK_PASSWORD_PEPPER`.
|
||||
- Задан надежный `CRANK_BOOTSTRAP_ADMIN_PASSWORD`.
|
||||
- Подготовлена локальная процедура `crank-migrate admin-auth bootstrap-create`;
|
||||
static startup-пароль администратора не используется в production.
|
||||
- Подготовлены защищённые local files для `crank-migrate admin-auth recover`:
|
||||
новый Admin password, текущий `CRANK_PASSWORD_PEPPER` и current master key.
|
||||
- `CRANK_BASE_URL` указывает на публичный HTTPS URL.
|
||||
- `CRANK_ENVIRONMENT=production`.
|
||||
- Если используется внешний приёмник критических ошибок, задан корректный
|
||||
@@ -24,10 +29,13 @@
|
||||
- `curl /health` для `mcp-server` возвращает `ok`.
|
||||
- `curl /ready` для `admin-api` и `mcp-server` возвращает `ready`.
|
||||
- UI открывается по публичному домену.
|
||||
- Вход под bootstrap admin работает.
|
||||
- Создан первый admin через одноразовый bootstrap token, повтор token
|
||||
отклоняется, обычный login работает.
|
||||
- Demo seed создал Frankfurter-пример, если `CRANK_DEMO_SEED=true`.
|
||||
- Тест операции `frankfurter_latest_rate` проходит.
|
||||
- MCP-клиент видит инструмент через агента `currency-rates`.
|
||||
- `admin-api` и `mcp-server` используют один active master-key identity; запуск
|
||||
с неверным `CRANK_MASTER_KEY` проверен как fail-closed на staging.
|
||||
- Каждая непустая строка stdout `admin-api` и `mcp-server` является
|
||||
корректным JSON и содержит `service`, `environment` и `event`.
|
||||
- При включённом канале контрольная критическая ошибка появляется у приёмника
|
||||
@@ -41,6 +49,9 @@
|
||||
- Удаляйте или отзывайте ключи, которые больше не используются.
|
||||
- Используйте secrets/auth profiles для токенов внешних API.
|
||||
- Не вставляйте реальные токены в статические заголовки операции.
|
||||
- Не меняйте `CRANK_MASTER_KEY` напрямую. Для смены ключа выполняйте
|
||||
`crank-migrate master-key preflight → rotate → verify → promote`, затем
|
||||
перезапускайте secret-using процессы с target key.
|
||||
|
||||
## Эксплуатация
|
||||
|
||||
|
||||
+47
-8
@@ -17,7 +17,7 @@ Crank настраивается через переменные окружен
|
||||
| `POSTGRES_ACQUIRE_TIMEOUT_MS` | `database.pool.acquire_timeout_ms` | `Shared` | `u64/milliseconds` | `5000` | `1..=300000` | `Public` | `Effective` |
|
||||
| `POSTGRES_IDLE_TIMEOUT_MS` | `database.pool.idle_timeout_ms` | `Shared` | `u64/milliseconds` | `600000` | `1000..=86400000` | `Public` | `Effective` |
|
||||
| `POSTGRES_MAX_LIFETIME_MS` | `database.pool.max_lifetime_ms` | `Shared` | `u64/milliseconds` | `1800000` | `1000..=86400000` | `Public` | `Effective` |
|
||||
| `CRANK_MASTER_KEY` | `runtime.master_key` | `Shared` | `secret/-` | `required/blank` | `-` | `Secret` | `Effective` |
|
||||
| `CRANK_MASTER_KEY` | `runtime.master_key` | `Shared` | `secret/-` | `required/blank` | `>=32` | `Secret` | `Effective` |
|
||||
| `CRANK_BASE_URL` | `runtime.base_url` | `Shared` | `url/-` | `blank` | `-` | `Internal` | `Effective` |
|
||||
| `CRANK_RUNTIME_MAX_CONCURRENT_UNARY` | `runtime.max_concurrent_unary` | `Shared` | `u32/requests` | `64` | `1..=65535` | `Public` | `Effective` |
|
||||
| `CRANK_CACHE_BACKEND` | `cache.backend` | `Shared` | `enum/-` | `memory` | `-` | `Public` | `Effective` |
|
||||
@@ -25,6 +25,7 @@ Crank настраивается через переменные окружен
|
||||
| `CRANK_CACHE_DEFAULT_TTL_MS` | `cache.default_ttl_ms` | `Shared` | `u64/milliseconds` | `blank` | `1..=86400000` | `Public` | `DeprecatedNoEffect` |
|
||||
| `CRANK_OUTBOUND_ALLOWED_HOSTS` | `outbound.allowed_hosts` | `Shared` | `host_list/-` | `` | `-` | `Internal` | `Effective` |
|
||||
| `CRANK_OUTBOUND_DENIED_HOSTS` | `outbound.denied_hosts` | `Shared` | `host_list/-` | `` | `-` | `Internal` | `Effective` |
|
||||
| `CRANK_OUTBOUND_MAX_REQUEST_BYTES` | `outbound.max_request_bytes` | `Shared` | `u64/bytes` | `4194304` | `1..=67108864` | `Public` | `Effective` |
|
||||
| `CRANK_OUTBOUND_MAX_RESPONSE_BYTES` | `outbound.max_response_bytes` | `Shared` | `u64/bytes` | `4194304` | `1..=67108864` | `Public` | `Effective` |
|
||||
| `CRANK_ENVIRONMENT` | `observability.environment` | `Shared` | `label/-` | `development` | `-` | `Public` | `Effective` |
|
||||
| `CRANK_LOG_LEVEL` | `observability.log_filter` | `Shared` | `string/-` | `blank` | `-` | `Public` | `Effective` |
|
||||
@@ -52,9 +53,10 @@ Crank настраивается через переменные окружен
|
||||
| `CRANK_SESSION_SECRET` | `admin.session.secret` | `AdminApi` | `secret/-` | `required/blank` | `-` | `Secret` | `Effective` |
|
||||
| `CRANK_PASSWORD_PEPPER` | `admin.password_pepper` | `AdminApi` | `secret/-` | `required/blank` | `-` | `Secret` | `Effective` |
|
||||
| `CRANK_SESSION_TTL_HOURS` | `admin.session.ttl_hours` | `AdminApi` | `u32/hours` | `24` | `1..=8760` | `Public` | `Effective` |
|
||||
| `CRANK_TRUST_FORWARDED_HEADERS` | `admin.trust_forwarded_headers` | `AdminApi` | `bool/-` | `false` | `-` | `Public` | `Effective` |
|
||||
| `CRANK_TRUST_FORWARDED_HEADERS` | `admin.trust_forwarded_headers` | `AdminApi` | `bool/-` | `blank` | `-` | `Public` | `DeprecatedNoEffect` |
|
||||
| `CRANK_TRUSTED_PROXY_IPS` | `admin.trusted_proxy_ips` | `AdminApi` | `ip_list/-` | `` | `-` | `Internal` | `Effective` |
|
||||
| `CRANK_BOOTSTRAP_ADMIN_EMAIL` | `admin.bootstrap.email` | `AdminApi` | `string/-` | `required/blank` | `-` | `Internal` | `Effective` |
|
||||
| `CRANK_BOOTSTRAP_ADMIN_PASSWORD` | `admin.bootstrap.password` | `AdminApi` | `secret/-` | `required/blank` | `-` | `Secret` | `Effective` |
|
||||
| `CRANK_BOOTSTRAP_ADMIN_PASSWORD` | `admin.bootstrap.password` | `AdminApi` | `secret/-` | `blank` | `-` | `Secret` | `Effective` |
|
||||
| `CRANK_BOOTSTRAP_ADMIN_DISPLAY_NAME` | `admin.bootstrap.display_name` | `AdminApi` | `string/-` | `Crank Owner` | `-` | `Internal` | `Effective` |
|
||||
| `CRANK_DEMO_SEED` | `admin.demo_seed` | `AdminApi` | `bool/-` | `false` | `-` | `Public` | `Effective` |
|
||||
| `CRANK_MCP_BIND` | `mcp.bind` | `McpServer` | `socket/-` | `0.0.0.0:3002` | `-` | `Internal` | `Effective` |
|
||||
@@ -110,24 +112,58 @@ CRANK_PUBLISH_BIND=127.0.0.1
|
||||
|
||||
```env
|
||||
CRANK_PUBLISH_BIND=0.0.0.0
|
||||
CRANK_TRUSTED_PROXY_IPS=192.0.2.10
|
||||
```
|
||||
|
||||
`CRANK_TRUSTED_PROXY_IPS` — comma-separated allowlist immediate peer IPs.
|
||||
Только эти peers могут задавать `X-Real-IP`/`X-Forwarded-For`; от всех
|
||||
остальных клиентов forwarding headers игнорируются. Старый
|
||||
`CRANK_TRUST_FORWARDED_HEADERS` оставлен только как deprecated/no-effect
|
||||
совместимость и не включает доверие к proxy.
|
||||
|
||||
## Авторизация администратора
|
||||
|
||||
- `CRANK_SESSION_SECRET` - ключ подписи браузерных сессий.
|
||||
- `CRANK_PASSWORD_PEPPER` - дополнительный секрет для хэширования паролей.
|
||||
- `CRANK_SESSION_TTL_HOURS` - срок жизни сессии в часах.
|
||||
- `CRANK_BOOTSTRAP_ADMIN_EMAIL` - email первого пользователя.
|
||||
- `CRANK_BOOTSTRAP_ADMIN_PASSWORD` - пароль первого пользователя.
|
||||
- `CRANK_BOOTSTRAP_ADMIN_EMAIL` - email для локального bootstrap-контракта первого пользователя.
|
||||
- `CRANK_BOOTSTRAP_ADMIN_DISPLAY_NAME` - отображаемое имя первого пользователя.
|
||||
- `CRANK_BOOTSTRAP_ADMIN_PASSWORD` - deprecated compatibility-поле для старых dev/demo запусков; production startup не должен создавать или обновлять администратора из этого значения.
|
||||
|
||||
Первый пользователь создается или обновляется при старте `admin-api`.
|
||||
Первый production-admin создается локальной операторской командой, а не
|
||||
статическим startup-паролем:
|
||||
|
||||
```bash
|
||||
crank-migrate admin-auth bootstrap-create --email owner@example.local
|
||||
```
|
||||
|
||||
Команда выводит одноразовый bootstrap token. Оператор вводит его на странице
|
||||
логина и задает первый пароль. Повторное использование token отклоняется.
|
||||
|
||||
## Шифрование секретов
|
||||
|
||||
- `CRANK_MASTER_KEY` - ключ шифрования сохраненных секретов.
|
||||
- `CRANK_MASTER_KEY` - ключ шифрования сохраненных секретов. Минимум 32 bytes;
|
||||
генерируйте случайное значение и храните его вне product backup set.
|
||||
|
||||
Этот ключ нужен `admin-api` и `mcp-server`. Если изменить ключ без миграции данных, ранее сохраненные секреты нельзя будет расшифровать.
|
||||
Этот ключ нужен `admin-api` и `mcp-server`. Оба процесса до readiness
|
||||
сравнивают non-secret fingerprint/epoch с PostgreSQL identity registry. Если
|
||||
ключ не совпадает с активной identity, startup завершается bounded diagnostic
|
||||
`master_key_identity_mismatch` без вывода raw key, fingerprint fragment,
|
||||
ciphertext или decrypt details.
|
||||
|
||||
Не меняйте значение `CRANK_MASTER_KEY` напрямую. Используйте операторскую
|
||||
процедуру:
|
||||
|
||||
```bash
|
||||
crank-migrate master-key preflight --current-key-file /secure/current.key --target-key-file /secure/target.key
|
||||
crank-migrate master-key rotate --current-key-file /secure/current.key --target-key-file /secure/target.key
|
||||
crank-migrate master-key verify --target-key-file /secure/target.key
|
||||
crank-migrate master-key promote --target-key-file /secure/target.key
|
||||
```
|
||||
|
||||
Команда читает database config через `CRANK_DATABASE_URL`/`POSTGRES_*`, а
|
||||
master keys — только из указанных локальных файлов. Значения ключей не должны
|
||||
передаваться как shell arguments или попадать в committed `.env`.
|
||||
|
||||
## Демо-данные
|
||||
|
||||
@@ -165,6 +201,8 @@ HTTP-перенаправления и системный прокси откл
|
||||
укажите его имя или IP явно. Поддерживаются маски вида `*.example.internal`.
|
||||
- `CRANK_OUTBOUND_DENIED_HOSTS` - список узлов, запрещённых независимо от списка
|
||||
разрешённых.
|
||||
- `CRANK_OUTBOUND_MAX_REQUEST_BYTES` - максимальный размер JSON-тела запроса к
|
||||
внешнему API; проверяется до отправки байтов.
|
||||
- `CRANK_OUTBOUND_MAX_RESPONSE_BYTES` - максимальный размер ответа внешнего API;
|
||||
по умолчанию `4194304` байт.
|
||||
|
||||
@@ -173,6 +211,7 @@ HTTP-перенаправления и системный прокси откл
|
||||
```env
|
||||
CRANK_OUTBOUND_ALLOWED_HOSTS=crm.example.internal,192.168.1.50
|
||||
CRANK_OUTBOUND_DENIED_HOSTS=metadata.example.internal
|
||||
CRANK_OUTBOUND_MAX_REQUEST_BYTES=4194304
|
||||
CRANK_OUTBOUND_MAX_RESPONSE_BYTES=4194304
|
||||
```
|
||||
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"budget": {"logical_series": 4513, "rendered_series": 14338, "max_logical_series": 5000, "max_rendered_series": 15000},
|
||||
"budget": {"logical_series": 5213, "rendered_series": 16538, "max_logical_series": 5250, "max_rendered_series": 16750},
|
||||
"global_labels": [
|
||||
{"name": "service", "class": "process_constant", "domain": ["admin-api", "mcp-server"]},
|
||||
{"name": "version", "class": "process_constant", "domain": ["validated_release_identity"]},
|
||||
{"name": "environment", "class": "process_constant", "domain": ["validated_startup_value"]}
|
||||
],
|
||||
"metrics": [
|
||||
{"name": "crank_http_requests_total", "kind": "counter", "unit": "count", "help": "Total HTTP requests.", "processes": ["admin_api", "mcp_server"], "labels": [{"name": "route", "class": "product_closed", "domain": ["unmatched", "/health", "/ready", "/api/auth/login", "/api/auth/logout", "/api/auth/session", "/api/auth/profile", "/api/auth/password", "/api/admin/capabilities", "/api/admin/workspaces", "/api/admin/workspaces/{workspace_id}", "/api/admin/workspaces/{workspace_id}/operations", "/api/admin/workspaces/{workspace_id}/imports/openapi/preview", "/api/admin/workspaces/{workspace_id}/imports/openapi/{job_id}/create", "/api/admin/workspaces/{workspace_id}/operations/analyze-quality", "/api/admin/workspaces/{workspace_id}/operations/import", "/api/admin/workspaces/{workspace_id}/operations/{operation_id}", "/api/admin/workspaces/{workspace_id}/operations/{operation_id}/versions", "/api/admin/workspaces/{workspace_id}/operations/{operation_id}/versions/{version}", "/api/admin/workspaces/{workspace_id}/operations/{operation_id}/publish", "/api/admin/workspaces/{workspace_id}/operations/{operation_id}/archive", "/api/admin/workspaces/{workspace_id}/operations/{operation_id}/test-runs", "/api/admin/workspaces/{workspace_id}/operations/{operation_id}/samples/input-json", "/api/admin/workspaces/{workspace_id}/operations/{operation_id}/samples/output-json", "/api/admin/workspaces/{workspace_id}/operations/{operation_id}/drafts/generate", "/api/admin/workspaces/{workspace_id}/operations/{operation_id}/export", "/api/admin/workspaces/{workspace_id}/agents", "/api/admin/workspaces/{workspace_id}/agents/tool-search/preview", "/api/admin/workspaces/{workspace_id}/agents/{agent_id}", "/api/admin/workspaces/{workspace_id}/agents/{agent_id}/versions/{version}", "/api/admin/workspaces/{workspace_id}/agents/{agent_id}/bindings", "/api/admin/workspaces/{workspace_id}/agents/{agent_id}/publish", "/api/admin/workspaces/{workspace_id}/agents/{agent_id}/unpublish", "/api/admin/workspaces/{workspace_id}/agents/{agent_id}/archive", "/api/admin/workspaces/{workspace_id}/agents/{agent_id}/platform-api-keys", "/api/admin/workspaces/{workspace_id}/agents/{agent_id}/platform-api-keys/{key_id}/revoke", "/api/admin/workspaces/{workspace_id}/agents/{agent_id}/platform-api-keys/{key_id}", "/api/admin/workspaces/{workspace_id}/auth-profiles", "/api/admin/workspaces/{workspace_id}/auth-profiles/{auth_profile_id}", "/api/admin/workspaces/{workspace_id}/upstreams", "/api/admin/workspaces/{workspace_id}/upstreams/{upstream_id}", "/api/admin/workspaces/{workspace_id}/secrets", "/api/admin/workspaces/{workspace_id}/secrets/{secret_id}", "/api/admin/workspaces/{workspace_id}/secrets/{secret_id}/rotate", "/api/admin/workspaces/{workspace_id}/export", "/api/admin/workspaces/{workspace_id}/logs", "/api/admin/workspaces/{workspace_id}/logs/{log_id}", "/api/admin/workspaces/{workspace_id}/approvals", "/api/admin/workspaces/{workspace_id}/approvals/{approval_id}", "/api/admin/workspaces/{workspace_id}/usage", "/api/admin/workspaces/{workspace_id}/usage/operations/{operation_id}", "/api/admin/workspaces/{workspace_id}/usage/agents/{agent_id}", "/v1/{workspace_slug}/{agent_slug}", "/v1/{workspace_slug}/{agent_slug}/approvals", "/v1/{workspace_slug}/{agent_slug}/approvals/{approval_id}/approve", "/v1/{workspace_slug}/{agent_slug}/approvals/{approval_id}", "/v1/{workspace_slug}/{agent_slug}/approvals/{approval_id}/deny"]},{"name": "method", "class": "product_closed", "domain": ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD", "CONNECT", "TRACE", "OTHER"]},{"name": "status_class", "class": "product_closed", "domain": ["1xx", "2xx", "3xx", "4xx", "5xx", "other"]}], "buckets_seconds": [], "max_logical_series": 3420, "max_rendered_series": 3420},
|
||||
{"name": "crank_http_request_duration_seconds", "kind": "histogram", "unit": "seconds", "help": "HTTP request duration in seconds.", "processes": ["admin_api", "mcp_server"], "labels": [{"name": "route", "class": "product_closed", "domain": ["unmatched", "/health", "/ready", "/api/auth/login", "/api/auth/logout", "/api/auth/session", "/api/auth/profile", "/api/auth/password", "/api/admin/capabilities", "/api/admin/workspaces", "/api/admin/workspaces/{workspace_id}", "/api/admin/workspaces/{workspace_id}/operations", "/api/admin/workspaces/{workspace_id}/imports/openapi/preview", "/api/admin/workspaces/{workspace_id}/imports/openapi/{job_id}/create", "/api/admin/workspaces/{workspace_id}/operations/analyze-quality", "/api/admin/workspaces/{workspace_id}/operations/import", "/api/admin/workspaces/{workspace_id}/operations/{operation_id}", "/api/admin/workspaces/{workspace_id}/operations/{operation_id}/versions", "/api/admin/workspaces/{workspace_id}/operations/{operation_id}/versions/{version}", "/api/admin/workspaces/{workspace_id}/operations/{operation_id}/publish", "/api/admin/workspaces/{workspace_id}/operations/{operation_id}/archive", "/api/admin/workspaces/{workspace_id}/operations/{operation_id}/test-runs", "/api/admin/workspaces/{workspace_id}/operations/{operation_id}/samples/input-json", "/api/admin/workspaces/{workspace_id}/operations/{operation_id}/samples/output-json", "/api/admin/workspaces/{workspace_id}/operations/{operation_id}/drafts/generate", "/api/admin/workspaces/{workspace_id}/operations/{operation_id}/export", "/api/admin/workspaces/{workspace_id}/agents", "/api/admin/workspaces/{workspace_id}/agents/tool-search/preview", "/api/admin/workspaces/{workspace_id}/agents/{agent_id}", "/api/admin/workspaces/{workspace_id}/agents/{agent_id}/versions/{version}", "/api/admin/workspaces/{workspace_id}/agents/{agent_id}/bindings", "/api/admin/workspaces/{workspace_id}/agents/{agent_id}/publish", "/api/admin/workspaces/{workspace_id}/agents/{agent_id}/unpublish", "/api/admin/workspaces/{workspace_id}/agents/{agent_id}/archive", "/api/admin/workspaces/{workspace_id}/agents/{agent_id}/platform-api-keys", "/api/admin/workspaces/{workspace_id}/agents/{agent_id}/platform-api-keys/{key_id}/revoke", "/api/admin/workspaces/{workspace_id}/agents/{agent_id}/platform-api-keys/{key_id}", "/api/admin/workspaces/{workspace_id}/auth-profiles", "/api/admin/workspaces/{workspace_id}/auth-profiles/{auth_profile_id}", "/api/admin/workspaces/{workspace_id}/upstreams", "/api/admin/workspaces/{workspace_id}/upstreams/{upstream_id}", "/api/admin/workspaces/{workspace_id}/secrets", "/api/admin/workspaces/{workspace_id}/secrets/{secret_id}", "/api/admin/workspaces/{workspace_id}/secrets/{secret_id}/rotate", "/api/admin/workspaces/{workspace_id}/export", "/api/admin/workspaces/{workspace_id}/logs", "/api/admin/workspaces/{workspace_id}/logs/{log_id}", "/api/admin/workspaces/{workspace_id}/approvals", "/api/admin/workspaces/{workspace_id}/approvals/{approval_id}", "/api/admin/workspaces/{workspace_id}/usage", "/api/admin/workspaces/{workspace_id}/usage/operations/{operation_id}", "/api/admin/workspaces/{workspace_id}/usage/agents/{agent_id}", "/v1/{workspace_slug}/{agent_slug}", "/v1/{workspace_slug}/{agent_slug}/approvals", "/v1/{workspace_slug}/{agent_slug}/approvals/{approval_id}/approve", "/v1/{workspace_slug}/{agent_slug}/approvals/{approval_id}", "/v1/{workspace_slug}/{agent_slug}/approvals/{approval_id}/deny"]},{"name": "method", "class": "product_closed", "domain": ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD", "CONNECT", "TRACE", "OTHER"]}], "buckets_seconds": [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10, 30, 60], "max_logical_series": 570, "max_rendered_series": 9120},
|
||||
{"name": "crank_http_requests_total", "kind": "counter", "unit": "count", "help": "Total HTTP requests.", "processes": ["admin_api", "mcp_server"], "labels": [{"name": "route", "class": "product_closed", "domain": ["unmatched", "/health", "/ready", "/api/auth/login", "/api/auth/bootstrap/status", "/api/auth/bootstrap/complete", "/api/auth/logout", "/api/auth/session", "/api/auth/session/csrf", "/api/auth/profile", "/api/auth/password", "/api/admin/capabilities", "/api/admin/workspaces", "/api/admin/workspaces/{workspace_id}", "/api/admin/workspaces/{workspace_id}/onboarding", "/api/admin/workspaces/{workspace_id}/onboarding/events", "/api/admin/workspaces/{workspace_id}/onboarding/reset-selection", "/api/admin/workspaces/{workspace_id}/operations", "/api/admin/workspaces/{workspace_id}/imports/openapi/preview", "/api/admin/workspaces/{workspace_id}/imports/openapi/{job_id}/create", "/api/admin/workspaces/{workspace_id}/operations/analyze-quality", "/api/admin/workspaces/{workspace_id}/operations/import", "/api/admin/workspaces/{workspace_id}/operations/{operation_id}", "/api/admin/workspaces/{workspace_id}/operations/{operation_id}/versions", "/api/admin/workspaces/{workspace_id}/operations/{operation_id}/versions/{version}", "/api/admin/workspaces/{workspace_id}/operations/{operation_id}/publish", "/api/admin/workspaces/{workspace_id}/operations/{operation_id}/archive", "/api/admin/workspaces/{workspace_id}/operations/{operation_id}/test-runs", "/api/admin/workspaces/{workspace_id}/operations/{operation_id}/samples/input-json", "/api/admin/workspaces/{workspace_id}/operations/{operation_id}/samples/output-json", "/api/admin/workspaces/{workspace_id}/operations/{operation_id}/drafts/generate", "/api/admin/workspaces/{workspace_id}/operations/{operation_id}/export", "/api/admin/workspaces/{workspace_id}/agents", "/api/admin/workspaces/{workspace_id}/agents/tool-search/preview", "/api/admin/workspaces/{workspace_id}/agents/{agent_id}", "/api/admin/workspaces/{workspace_id}/agents/{agent_id}/versions/{version}", "/api/admin/workspaces/{workspace_id}/agents/{agent_id}/bindings", "/api/admin/workspaces/{workspace_id}/agents/{agent_id}/publish", "/api/admin/workspaces/{workspace_id}/agents/{agent_id}/unpublish", "/api/admin/workspaces/{workspace_id}/agents/{agent_id}/archive", "/api/admin/workspaces/{workspace_id}/agents/{agent_id}/platform-api-keys", "/api/admin/workspaces/{workspace_id}/agents/{agent_id}/platform-api-keys/{key_id}/revoke", "/api/admin/workspaces/{workspace_id}/agents/{agent_id}/platform-api-keys/{key_id}", "/api/admin/workspaces/{workspace_id}/auth-profiles", "/api/admin/workspaces/{workspace_id}/auth-profiles/{auth_profile_id}", "/api/admin/workspaces/{workspace_id}/upstreams", "/api/admin/workspaces/{workspace_id}/upstreams/{upstream_id}", "/api/admin/workspaces/{workspace_id}/secrets", "/api/admin/workspaces/{workspace_id}/secrets/{secret_id}", "/api/admin/workspaces/{workspace_id}/secrets/{secret_id}/rotate", "/api/admin/workspaces/{workspace_id}/export", "/api/admin/workspaces/{workspace_id}/logs", "/api/admin/workspaces/{workspace_id}/logs/export.csv", "/api/admin/workspaces/{workspace_id}/logs/{log_id}", "/api/admin/workspaces/{workspace_id}/approvals", "/api/admin/workspaces/{workspace_id}/approvals/{approval_id}", "/api/admin/workspaces/{workspace_id}/approvals/{approval_id}/approve", "/api/admin/workspaces/{workspace_id}/approvals/{approval_id}/deny", "/api/admin/workspaces/{workspace_id}/usage", "/api/admin/workspaces/{workspace_id}/usage/export.csv", "/api/admin/workspaces/{workspace_id}/usage/operations/{operation_id}", "/api/admin/workspaces/{workspace_id}/usage/agents/{agent_id}", "/v1/{workspace_slug}/{agent_slug}", "/v1/{workspace_slug}/{agent_slug}/approvals", "/v1/{workspace_slug}/{agent_slug}/approvals/{approval_id}/approve", "/v1/{workspace_slug}/{agent_slug}/approvals/{approval_id}", "/v1/{workspace_slug}/{agent_slug}/approvals/{approval_id}/deny"]},{"name": "method", "class": "product_closed", "domain": ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD", "CONNECT", "TRACE", "OTHER"]},{"name": "status_class", "class": "product_closed", "domain": ["1xx", "2xx", "3xx", "4xx", "5xx", "other"]}], "buckets_seconds": [], "max_logical_series": 4020, "max_rendered_series": 4020},
|
||||
{"name": "crank_http_request_duration_seconds", "kind": "histogram", "unit": "seconds", "help": "HTTP request duration in seconds.", "processes": ["admin_api", "mcp_server"], "labels": [{"name": "route", "class": "product_closed", "domain": ["unmatched", "/health", "/ready", "/api/auth/login", "/api/auth/bootstrap/status", "/api/auth/bootstrap/complete", "/api/auth/logout", "/api/auth/session", "/api/auth/session/csrf", "/api/auth/profile", "/api/auth/password", "/api/admin/capabilities", "/api/admin/workspaces", "/api/admin/workspaces/{workspace_id}", "/api/admin/workspaces/{workspace_id}/onboarding", "/api/admin/workspaces/{workspace_id}/onboarding/events", "/api/admin/workspaces/{workspace_id}/onboarding/reset-selection", "/api/admin/workspaces/{workspace_id}/operations", "/api/admin/workspaces/{workspace_id}/imports/openapi/preview", "/api/admin/workspaces/{workspace_id}/imports/openapi/{job_id}/create", "/api/admin/workspaces/{workspace_id}/operations/analyze-quality", "/api/admin/workspaces/{workspace_id}/operations/import", "/api/admin/workspaces/{workspace_id}/operations/{operation_id}", "/api/admin/workspaces/{workspace_id}/operations/{operation_id}/versions", "/api/admin/workspaces/{workspace_id}/operations/{operation_id}/versions/{version}", "/api/admin/workspaces/{workspace_id}/operations/{operation_id}/publish", "/api/admin/workspaces/{workspace_id}/operations/{operation_id}/archive", "/api/admin/workspaces/{workspace_id}/operations/{operation_id}/test-runs", "/api/admin/workspaces/{workspace_id}/operations/{operation_id}/samples/input-json", "/api/admin/workspaces/{workspace_id}/operations/{operation_id}/samples/output-json", "/api/admin/workspaces/{workspace_id}/operations/{operation_id}/drafts/generate", "/api/admin/workspaces/{workspace_id}/operations/{operation_id}/export", "/api/admin/workspaces/{workspace_id}/agents", "/api/admin/workspaces/{workspace_id}/agents/tool-search/preview", "/api/admin/workspaces/{workspace_id}/agents/{agent_id}", "/api/admin/workspaces/{workspace_id}/agents/{agent_id}/versions/{version}", "/api/admin/workspaces/{workspace_id}/agents/{agent_id}/bindings", "/api/admin/workspaces/{workspace_id}/agents/{agent_id}/publish", "/api/admin/workspaces/{workspace_id}/agents/{agent_id}/unpublish", "/api/admin/workspaces/{workspace_id}/agents/{agent_id}/archive", "/api/admin/workspaces/{workspace_id}/agents/{agent_id}/platform-api-keys", "/api/admin/workspaces/{workspace_id}/agents/{agent_id}/platform-api-keys/{key_id}/revoke", "/api/admin/workspaces/{workspace_id}/agents/{agent_id}/platform-api-keys/{key_id}", "/api/admin/workspaces/{workspace_id}/auth-profiles", "/api/admin/workspaces/{workspace_id}/auth-profiles/{auth_profile_id}", "/api/admin/workspaces/{workspace_id}/upstreams", "/api/admin/workspaces/{workspace_id}/upstreams/{upstream_id}", "/api/admin/workspaces/{workspace_id}/secrets", "/api/admin/workspaces/{workspace_id}/secrets/{secret_id}", "/api/admin/workspaces/{workspace_id}/secrets/{secret_id}/rotate", "/api/admin/workspaces/{workspace_id}/export", "/api/admin/workspaces/{workspace_id}/logs", "/api/admin/workspaces/{workspace_id}/logs/export.csv", "/api/admin/workspaces/{workspace_id}/logs/{log_id}", "/api/admin/workspaces/{workspace_id}/approvals", "/api/admin/workspaces/{workspace_id}/approvals/{approval_id}", "/api/admin/workspaces/{workspace_id}/approvals/{approval_id}/approve", "/api/admin/workspaces/{workspace_id}/approvals/{approval_id}/deny", "/api/admin/workspaces/{workspace_id}/usage", "/api/admin/workspaces/{workspace_id}/usage/export.csv", "/api/admin/workspaces/{workspace_id}/usage/operations/{operation_id}", "/api/admin/workspaces/{workspace_id}/usage/agents/{agent_id}", "/v1/{workspace_slug}/{agent_slug}", "/v1/{workspace_slug}/{agent_slug}/approvals", "/v1/{workspace_slug}/{agent_slug}/approvals/{approval_id}/approve", "/v1/{workspace_slug}/{agent_slug}/approvals/{approval_id}", "/v1/{workspace_slug}/{agent_slug}/approvals/{approval_id}/deny"]},{"name": "method", "class": "product_closed", "domain": ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD", "CONNECT", "TRACE", "OTHER"]}], "buckets_seconds": [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10, 30, 60], "max_logical_series": 670, "max_rendered_series": 10720},
|
||||
{"name": "crank_http_inflight", "kind": "gauge", "unit": "count", "help": "HTTP requests currently being processed.", "processes": ["admin_api", "mcp_server"], "labels": [], "buckets_seconds": [], "max_logical_series": 1, "max_rendered_series": 1},
|
||||
{"name": "crank_mcp_requests_total", "kind": "counter", "unit": "count", "help": "Total MCP JSON-RPC requests.", "processes": ["mcp_server"], "labels": [{"name": "method", "class": "product_closed", "domain": ["initialize", "initialized", "ping", "tools_list", "tools_call", "notification", "unsupported", "response", "invalid"]},{"name": "response_mode", "class": "product_closed", "domain": ["json", "sse", "unknown"]},{"name": "outcome", "class": "product_closed", "domain": ["success", "client_error", "server_error", "jsonrpc_error", "tool_error", "aborted", "other"]}], "buckets_seconds": [], "max_logical_series": 189, "max_rendered_series": 189},
|
||||
{"name": "crank_mcp_request_duration_seconds", "kind": "histogram", "unit": "seconds", "help": "MCP JSON-RPC request duration in seconds.", "processes": ["mcp_server"], "labels": [{"name": "method", "class": "product_closed", "domain": ["initialize", "initialized", "ping", "tools_list", "tools_call", "notification", "unsupported", "response", "invalid"]},{"name": "outcome", "class": "product_closed", "domain": ["success", "client_error", "server_error", "jsonrpc_error", "tool_error", "aborted", "other"]}], "buckets_seconds": [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10, 30, 60], "max_logical_series": 63, "max_rendered_series": 1008},
|
||||
|
||||
@@ -1 +1,181 @@
|
||||
{"schema_version":1,"sequence":[{"backfill":{"kind":"none"},"checksum":"crank-community-baseline-v1","compatibility":"legacy-baseline","contract_evidence":null,"name":"community-baseline-v1","owner":"crank-registry","phase":"expand","readable_schema_max":1,"readable_schema_min":1,"source_digest":"eb1656fc5b4b5be9ee390d237d1d58e4b2274ae5ba9b7ba06a2f3f860dfda675","transactional":true,"version":1},{"backfill":{"kind":"none"},"checksum":"1908b97ca7fe8a85d146b0eebf007d5018a9ff406cb099614270f3428ec1db48","compatibility":"n-minus-one-readable","contract_evidence":null,"name":"legacy-consolidation-v2","owner":"crank-registry","phase":"expand","readable_schema_max":2,"readable_schema_min":1,"source_digest":"1908b97ca7fe8a85d146b0eebf007d5018a9ff406cb099614270f3428ec1db48","transactional":true,"version":2},{"backfill":{"kind":"none"},"checksum":"36487625503a8d4d8f18d5771c3c9b6705f845e267acbfcb7244c330f640cd94","compatibility":"n-minus-one-readable","contract_evidence":null,"name":"request-trace-identity-v3","owner":"crank-registry","phase":"expand","readable_schema_max":3,"readable_schema_min":2,"source_digest":"36487625503a8d4d8f18d5771c3c9b6705f845e267acbfcb7244c330f640cd94","transactional":true,"version":3}]}
|
||||
{
|
||||
"schema_version": 1,
|
||||
"sequence": [
|
||||
{
|
||||
"backfill": {
|
||||
"kind": "none"
|
||||
},
|
||||
"checksum": "crank-community-baseline-v1",
|
||||
"compatibility": "legacy-baseline",
|
||||
"contract_evidence": null,
|
||||
"name": "community-baseline-v1",
|
||||
"owner": "crank-registry",
|
||||
"phase": "expand",
|
||||
"readable_schema_max": 1,
|
||||
"readable_schema_min": 1,
|
||||
"source_digest": "eb1656fc5b4b5be9ee390d237d1d58e4b2274ae5ba9b7ba06a2f3f860dfda675",
|
||||
"transactional": true,
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"backfill": {
|
||||
"kind": "none"
|
||||
},
|
||||
"checksum": "1908b97ca7fe8a85d146b0eebf007d5018a9ff406cb099614270f3428ec1db48",
|
||||
"compatibility": "n-minus-one-readable",
|
||||
"contract_evidence": null,
|
||||
"name": "legacy-consolidation-v2",
|
||||
"owner": "crank-registry",
|
||||
"phase": "expand",
|
||||
"readable_schema_max": 2,
|
||||
"readable_schema_min": 1,
|
||||
"source_digest": "1908b97ca7fe8a85d146b0eebf007d5018a9ff406cb099614270f3428ec1db48",
|
||||
"transactional": true,
|
||||
"version": 2
|
||||
},
|
||||
{
|
||||
"backfill": {
|
||||
"kind": "none"
|
||||
},
|
||||
"checksum": "36487625503a8d4d8f18d5771c3c9b6705f845e267acbfcb7244c330f640cd94",
|
||||
"compatibility": "n-minus-one-readable",
|
||||
"contract_evidence": null,
|
||||
"name": "request-trace-identity-v3",
|
||||
"owner": "crank-registry",
|
||||
"phase": "expand",
|
||||
"readable_schema_max": 3,
|
||||
"readable_schema_min": 2,
|
||||
"source_digest": "36487625503a8d4d8f18d5771c3c9b6705f845e267acbfcb7244c330f640cd94",
|
||||
"transactional": true,
|
||||
"version": 3
|
||||
},
|
||||
{
|
||||
"backfill": {
|
||||
"kind": "none"
|
||||
},
|
||||
"checksum": "45723712a1ea49cd8bbf59d77148c225f3ec376df7433983d982cc6f9d7fb39c",
|
||||
"compatibility": "n-minus-one-readable",
|
||||
"contract_evidence": null,
|
||||
"name": "operation-lifecycle-v4",
|
||||
"owner": "crank-registry",
|
||||
"phase": "expand",
|
||||
"readable_schema_max": 4,
|
||||
"readable_schema_min": 3,
|
||||
"source_digest": "45723712a1ea49cd8bbf59d77148c225f3ec376df7433983d982cc6f9d7fb39c",
|
||||
"transactional": true,
|
||||
"version": 4
|
||||
},
|
||||
{
|
||||
"backfill": {
|
||||
"kind": "none"
|
||||
},
|
||||
"checksum": "bd6703249cc407789586327eb7fbbd5776ba27923c5dc7eb85b05d332585bf42",
|
||||
"compatibility": "n-minus-one-readable",
|
||||
"contract_evidence": null,
|
||||
"name": "execution-outcome-v5",
|
||||
"owner": "crank-registry",
|
||||
"phase": "expand",
|
||||
"readable_schema_max": 5,
|
||||
"readable_schema_min": 4,
|
||||
"source_digest": "bd6703249cc407789586327eb7fbbd5776ba27923c5dc7eb85b05d332585bf42",
|
||||
"transactional": true,
|
||||
"version": 5
|
||||
},
|
||||
{
|
||||
"backfill": {
|
||||
"kind": "none"
|
||||
},
|
||||
"checksum": "94dba9b9dd3364607bc37e7698478ba6644607a7f268a1621cd3acea6bc96c2d",
|
||||
"compatibility": "n-minus-one-readable",
|
||||
"contract_evidence": null,
|
||||
"name": "platform-key-name-reuse-v6",
|
||||
"owner": "crank-registry",
|
||||
"phase": "expand",
|
||||
"readable_schema_max": 6,
|
||||
"readable_schema_min": 5,
|
||||
"source_digest": "94dba9b9dd3364607bc37e7698478ba6644607a7f268a1621cd3acea6bc96c2d",
|
||||
"transactional": true,
|
||||
"version": 6
|
||||
},
|
||||
{
|
||||
"backfill": {
|
||||
"kind": "none"
|
||||
},
|
||||
"checksum": "06e5d4667d9a7346474e9dcb176b8b2b9680c4b89089bea5a18eb5a88d5ac60e",
|
||||
"compatibility": "n-minus-one-readable",
|
||||
"contract_evidence": null,
|
||||
"name": "master-key-identity-v7",
|
||||
"owner": "crank-registry",
|
||||
"phase": "expand",
|
||||
"readable_schema_max": 7,
|
||||
"readable_schema_min": 6,
|
||||
"source_digest": "06e5d4667d9a7346474e9dcb176b8b2b9680c4b89089bea5a18eb5a88d5ac60e",
|
||||
"transactional": true,
|
||||
"version": 7
|
||||
},
|
||||
{
|
||||
"backfill": {
|
||||
"kind": "none"
|
||||
},
|
||||
"checksum": "6361f3321a1442a77c695cad9b30c4702de1aa3e565bfbf1a7415d653302611f",
|
||||
"compatibility": "n-minus-one-readable",
|
||||
"contract_evidence": null,
|
||||
"name": "admin-auth-lifecycle-v8",
|
||||
"owner": "crank-registry",
|
||||
"phase": "expand",
|
||||
"readable_schema_max": 8,
|
||||
"readable_schema_min": 7,
|
||||
"source_digest": "6361f3321a1442a77c695cad9b30c4702de1aa3e565bfbf1a7415d653302611f",
|
||||
"transactional": true,
|
||||
"version": 8
|
||||
},
|
||||
{
|
||||
"backfill": {
|
||||
"kind": "none"
|
||||
},
|
||||
"checksum": "b49bf4b53691407ec27e3810b0531c0d440da7dddc62ea71eb219933c0a30211",
|
||||
"compatibility": "n-minus-one-readable",
|
||||
"contract_evidence": null,
|
||||
"name": "agent-catalog-lifecycle-v9",
|
||||
"owner": "crank-registry",
|
||||
"phase": "expand",
|
||||
"readable_schema_max": 9,
|
||||
"readable_schema_min": 8,
|
||||
"source_digest": "b49bf4b53691407ec27e3810b0531c0d440da7dddc62ea71eb219933c0a30211",
|
||||
"transactional": true,
|
||||
"version": 9
|
||||
},
|
||||
{
|
||||
"backfill": {
|
||||
"kind": "none"
|
||||
},
|
||||
"checksum": "6c87f680efdbc275fa2b920826b3e3e390aa34d6f2f1db48fe074a5d7691ba5b",
|
||||
"compatibility": "n-minus-one-readable",
|
||||
"contract_evidence": null,
|
||||
"name": "approval-side-effects-v10",
|
||||
"owner": "crank-registry",
|
||||
"phase": "expand",
|
||||
"readable_schema_max": 10,
|
||||
"readable_schema_min": 9,
|
||||
"source_digest": "6c87f680efdbc275fa2b920826b3e3e390aa34d6f2f1db48fe074a5d7691ba5b",
|
||||
"transactional": true,
|
||||
"version": 10
|
||||
},
|
||||
{
|
||||
"backfill": {
|
||||
"kind": "none"
|
||||
},
|
||||
"checksum": "a439bbcdc9cc909d717ed5a868c7a51bd1ad3c49c4e85fd20933743ee3f31166",
|
||||
"compatibility": "n-minus-one-readable",
|
||||
"contract_evidence": null,
|
||||
"name": "onboarding-product-events-v11",
|
||||
"owner": "crank-registry",
|
||||
"phase": "expand",
|
||||
"readable_schema_max": 11,
|
||||
"readable_schema_min": 10,
|
||||
"source_digest": "a439bbcdc9cc909d717ed5a868c7a51bd1ad3c49c4e85fd20933743ee3f31166",
|
||||
"transactional": true,
|
||||
"version": 11
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://crank.local/schemas/operation-export-v2.schema.json",
|
||||
"title": "Crank portable Operation export v2",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["format_version", "kind", "operation"],
|
||||
"properties": {
|
||||
"format_version": { "const": "2" },
|
||||
"kind": { "const": "operation" },
|
||||
"operation": { "$ref": "#/$defs/portable_operation" }
|
||||
},
|
||||
"$defs": {
|
||||
"portable_operation": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["name", "display_name", "category", "protocol", "security_level", "target", "input_schema", "output_schema", "input_mapping", "output_mapping", "execution_config", "tool_description"],
|
||||
"properties": {
|
||||
"name": { "type": "string", "minLength": 1, "maxLength": 64 },
|
||||
"display_name": { "type": "string", "minLength": 1, "maxLength": 256 },
|
||||
"category": { "type": "string", "minLength": 1, "maxLength": 128 },
|
||||
"protocol": { "const": "rest" },
|
||||
"security_level": { "enum": ["standard"] },
|
||||
"target": { "$ref": "#/$defs/rest_target" },
|
||||
"input_schema": { "$ref": "#/$defs/schema" },
|
||||
"output_schema": { "$ref": "#/$defs/schema" },
|
||||
"input_mapping": { "$ref": "#/$defs/mapping_set" },
|
||||
"output_mapping": { "$ref": "#/$defs/mapping_set" },
|
||||
"execution_config": { "$ref": "#/$defs/execution_config" },
|
||||
"tool_description": { "$ref": "#/$defs/tool_description" }
|
||||
}
|
||||
},
|
||||
"string_map": { "type": "object", "propertyNames": { "minLength": 1, "maxLength": 256 }, "additionalProperties": { "type": "string", "maxLength": 65536 } },
|
||||
"rest_target": {
|
||||
"type": "object", "additionalProperties": false,
|
||||
"required": ["kind", "base_url", "method", "path_template"],
|
||||
"properties": {
|
||||
"kind": { "const": "rest" }, "base_url": { "type": "string", "minLength": 1, "maxLength": 4096 },
|
||||
"method": { "enum": ["get", "post", "put", "patch", "delete"] },
|
||||
"path_template": { "type": "string", "minLength": 1, "maxLength": 4096 },
|
||||
"static_headers": { "$ref": "#/$defs/string_map" }
|
||||
}
|
||||
},
|
||||
"schema": {
|
||||
"type": "object", "additionalProperties": false, "required": ["type"],
|
||||
"properties": {
|
||||
"type": { "enum": ["object", "array", "string", "integer", "number", "boolean", "enum", "null", "oneof"] },
|
||||
"description": { "type": "string", "maxLength": 65536 }, "required": { "type": "boolean" }, "nullable": { "type": "boolean" },
|
||||
"default_value": {}, "fields": { "type": "object", "maxProperties": 4096, "additionalProperties": { "$ref": "#/$defs/schema" } },
|
||||
"items": { "$ref": "#/$defs/schema" }, "enum_values": { "type": "array", "maxItems": 4096, "items": { "type": "string", "maxLength": 65536 } },
|
||||
"variants": { "type": "array", "maxItems": 4096, "items": { "$ref": "#/$defs/schema" } }
|
||||
}
|
||||
},
|
||||
"mapping_set": { "type": "object", "additionalProperties": false, "properties": { "rules": { "type": "array", "maxItems": 4096, "items": { "$ref": "#/$defs/mapping_rule" } } } },
|
||||
"mapping_rule": {
|
||||
"type": "object", "additionalProperties": false, "required": ["source", "target"],
|
||||
"properties": {
|
||||
"source": { "type": "string", "maxLength": 4096 }, "target": { "type": "string", "maxLength": 4096 }, "required": { "type": "boolean" }, "default_value": {},
|
||||
"transform": { "type": "object", "additionalProperties": false, "required": ["kind"], "properties": { "kind": { "enum": ["identity", "to_string", "to_number", "to_boolean", "join", "split", "wrap_array", "unwrap_singleton"] } } },
|
||||
"condition": { "type": "object", "additionalProperties": false, "required": ["source", "equals"], "properties": { "source": { "type": "string", "maxLength": 4096 }, "equals": {} } },
|
||||
"notes": { "type": "string", "maxLength": 65536 }
|
||||
}
|
||||
},
|
||||
"execution_config": {
|
||||
"type": "object", "additionalProperties": false, "required": ["timeout_ms"],
|
||||
"properties": {
|
||||
"timeout_ms": { "type": "integer", "minimum": 1 },
|
||||
"retry_policy": { "type": "object", "additionalProperties": false, "required": ["max_attempts"], "properties": { "max_attempts": { "type": "integer", "minimum": 0 } } },
|
||||
"response_cache": { "type": "object", "additionalProperties": false, "required": ["ttl_ms"], "properties": { "ttl_ms": { "type": "integer", "minimum": 1 } } },
|
||||
"idempotency": { "type": "object", "additionalProperties": false, "required": ["mode", "ttl_ms"], "properties": { "mode": { "enum": ["disabled", "optional", "required"] }, "ttl_ms": { "type": "integer", "minimum": 1 }, "input_field": { "type": "string" }, "header_name": { "type": "string" } } },
|
||||
"safety": { "type": "object", "additionalProperties": false, "required": ["class"], "properties": { "class": { "enum": ["read", "write", "destructive", "external_message", "financial_or_irreversible"] }, "confirmation": { "type": "object", "additionalProperties": false, "required": ["ttl_ms"], "properties": { "ttl_ms": { "type": "integer", "minimum": 1 } } } } },
|
||||
"approval_policy": { "type": "object", "additionalProperties": false, "required": ["required", "ttl_seconds", "show_payload_preview", "payload_preview_mode"], "properties": { "required": { "type": "boolean" }, "mode": { "enum": ["custom", "elicitation"] }, "risk_level": { "enum": ["normal", "dangerous", "financial", "irreversible"] }, "ttl_seconds": { "type": "integer", "minimum": 1 }, "show_payload_preview": { "type": "boolean" }, "payload_preview_mode": { "enum": ["summary", "masked_json"] }, "elicitation_message": { "type": "string" } } },
|
||||
"auth_profile_ref": { "type": "string", "minLength": 1, "maxLength": 256 }, "headers": { "$ref": "#/$defs/string_map" }
|
||||
}
|
||||
},
|
||||
"tool_description": {
|
||||
"type": "object", "additionalProperties": false, "required": ["title", "description"],
|
||||
"properties": { "title": { "type": "string", "maxLength": 256 }, "description": { "type": "string", "maxLength": 65536 }, "tags": { "type": "array", "maxItems": 4096, "items": { "type": "string", "maxLength": 256 } }, "examples": { "type": "array", "maxItems": 4096, "items": { "type": "object", "additionalProperties": false, "required": ["input"], "properties": { "input": {} } } } }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -182,7 +182,7 @@
|
||||
"unit": null,
|
||||
"default": null,
|
||||
"required": true,
|
||||
"minimum": null,
|
||||
"minimum": 32,
|
||||
"maximum": null,
|
||||
"sensitivity": "secret",
|
||||
"mode": "effective",
|
||||
@@ -300,6 +300,21 @@
|
||||
"deny entries override allow entries"
|
||||
]
|
||||
},
|
||||
{
|
||||
"semantic_path": "outbound.max_request_bytes",
|
||||
"env_name": "CRANK_OUTBOUND_MAX_REQUEST_BYTES",
|
||||
"process": "shared",
|
||||
"value_type": "u64",
|
||||
"unit": "bytes",
|
||||
"default": "4194304",
|
||||
"required": false,
|
||||
"minimum": 1,
|
||||
"maximum": 67108864,
|
||||
"sensitivity": "public",
|
||||
"mode": "effective",
|
||||
"compatibility": null,
|
||||
"rules": []
|
||||
},
|
||||
{
|
||||
"semantic_path": "outbound.max_response_bytes",
|
||||
"env_name": "CRANK_OUTBOUND_MAX_RESPONSE_BYTES",
|
||||
@@ -719,15 +734,33 @@
|
||||
"process": "admin_api",
|
||||
"value_type": "bool",
|
||||
"unit": null,
|
||||
"default": "false",
|
||||
"default": null,
|
||||
"required": false,
|
||||
"minimum": null,
|
||||
"maximum": null,
|
||||
"sensitivity": "public",
|
||||
"mode": "effective",
|
||||
"compatibility": "yes/no/on/off spellings are deprecated",
|
||||
"mode": "deprecated_no_effect",
|
||||
"compatibility": "deprecated boolean proxy trust; use CRANK_TRUSTED_PROXY_IPS",
|
||||
"rules": []
|
||||
},
|
||||
{
|
||||
"semantic_path": "admin.trusted_proxy_ips",
|
||||
"env_name": "CRANK_TRUSTED_PROXY_IPS",
|
||||
"process": "admin_api",
|
||||
"value_type": "ip_list",
|
||||
"unit": null,
|
||||
"default": "",
|
||||
"required": false,
|
||||
"minimum": null,
|
||||
"maximum": null,
|
||||
"sensitivity": "internal",
|
||||
"mode": "effective",
|
||||
"compatibility": null,
|
||||
"rules": [
|
||||
"only listed immediate peer IPs may supply X-Real-IP/X-Forwarded-For client identity",
|
||||
"empty value disables forwarded-header trust"
|
||||
]
|
||||
},
|
||||
{
|
||||
"semantic_path": "admin.bootstrap.email",
|
||||
"env_name": "CRANK_BOOTSTRAP_ADMIN_EMAIL",
|
||||
@@ -750,12 +783,12 @@
|
||||
"value_type": "secret",
|
||||
"unit": null,
|
||||
"default": null,
|
||||
"required": true,
|
||||
"required": false,
|
||||
"minimum": null,
|
||||
"maximum": null,
|
||||
"sensitivity": "secret",
|
||||
"mode": "effective",
|
||||
"compatibility": null,
|
||||
"compatibility": "deprecated startup-bootstrap password; use local bootstrap contract",
|
||||
"rules": []
|
||||
},
|
||||
{
|
||||
|
||||
@@ -2,6 +2,37 @@
|
||||
|
||||
Crank может вызывать REST API без авторизации или с авторизацией через сохраненный секрет.
|
||||
|
||||
## Администратор и browser sessions
|
||||
|
||||
Первый production-admin не создаётся из static startup password. Оператор
|
||||
создаёт локальный bootstrap-контракт командой:
|
||||
|
||||
```bash
|
||||
crank-migrate admin-auth bootstrap-create --email owner@example.local
|
||||
```
|
||||
|
||||
Команда выводит одноразовый token. Token вводится на `/login` вместе с первым
|
||||
паролем; повторное использование или истёкший token отклоняется одинаковой
|
||||
ошибкой без раскрытия причины.
|
||||
|
||||
После login сервер устанавливает HttpOnly session cookie и возвращает
|
||||
`csrf_token`. Все unsafe browser mutations под `/api/auth/*` и `/api/admin/*`
|
||||
требуют `x-csrf-token`; cross-origin `/api/*` requests отклоняются по
|
||||
умолчанию. Смена пароля отзывает активные browser sessions и CSRF state.
|
||||
|
||||
Если Admin password потерян, оператор выполняет recovery локально. Команда
|
||||
проверяет active master-key identity, заменяет verifier существующего Admin
|
||||
account и отзывает все browser sessions; старый пароль, Secrets и key material
|
||||
не раскрываются:
|
||||
|
||||
```bash
|
||||
crank-migrate admin-auth recover \
|
||||
--email owner@example.local \
|
||||
--password-file /secure/new-admin-password.txt \
|
||||
--password-pepper-file /secure/password-pepper.txt \
|
||||
--master-key-file /secure/current-master.key
|
||||
```
|
||||
|
||||
## Секреты
|
||||
|
||||
Поддерживаемые типы:
|
||||
@@ -11,7 +42,70 @@ Crank может вызывать REST API без авторизации или
|
||||
- значение HTTP-заголовка;
|
||||
- произвольный JSON.
|
||||
|
||||
Секреты шифруются ключом `CRANK_MASTER_KEY`. После создания или ротации значение нельзя прочитать через UI или API.
|
||||
Секреты шифруются ключом `CRANK_MASTER_KEY`. Значение должно быть не короче
|
||||
32 bytes; слабый или пустой ключ отклоняется до product readiness. После
|
||||
создания или ротации значение секрета нельзя прочитать через UI или API.
|
||||
|
||||
PostgreSQL хранит только encrypted secret value, `key_version` и master-key
|
||||
epoch. Сам `CRANK_MASTER_KEY`, derived key bytes и plaintext Secret не
|
||||
сохраняются и не выводятся в diagnostics.
|
||||
|
||||
## Master-key identity и rotation
|
||||
|
||||
После миграции schema до текущей версии первый secret-using process
|
||||
регистрирует в PostgreSQL non-secret identity активного master key:
|
||||
|
||||
- epoch;
|
||||
- fingerprint;
|
||||
- cipher contract.
|
||||
|
||||
`admin-api` и `mcp-server` проверяют эту identity до product readiness. Если
|
||||
process запускается на populated database без identity, он сначала доказывает,
|
||||
что текущий key расшифровывает все существующие Secret versions, и только затем
|
||||
регистрирует fingerprint. Если процесс запущен с другим `CRANK_MASTER_KEY`,
|
||||
startup завершается безопасной ошибкой `master_key_identity_mismatch`;
|
||||
сохранённые Secrets при этом не перезаписываются.
|
||||
|
||||
Ротация master key выполняется только локальной операторской командой
|
||||
`crank-migrate master-key ...`. Ключевой материал передаётся через локальные
|
||||
файлы, а не через аргументы со значениями:
|
||||
|
||||
```bash
|
||||
crank-migrate master-key preflight \
|
||||
--current-key-file /secure/current.key \
|
||||
--target-key-file /secure/target.key \
|
||||
--backup-ref offline-backup-ref
|
||||
|
||||
crank-migrate master-key rotate \
|
||||
--current-key-file /secure/current.key \
|
||||
--target-key-file /secure/target.key \
|
||||
--backup-ref offline-backup-ref
|
||||
|
||||
crank-migrate master-key verify --target-key-file /secure/target.key
|
||||
crank-migrate master-key promote --target-key-file /secure/target.key
|
||||
```
|
||||
|
||||
`preflight` read-only: проверяет текущий ключ, decryptability существующих
|
||||
Secret versions, уникальность target fingerprint и отсутствие активной
|
||||
rotation. `rotate` создаёт durable rotation record и target ciphertext, не
|
||||
удаляя текущий ciphertext. Если команда прерывается, повторный `rotate`
|
||||
продолжает обработку по сохранённому checkpoint. `verify` должен успешно
|
||||
расшифровать все target ciphertext до `promote`.
|
||||
|
||||
До promotion новые create/rotate Secret writes fail-closed с
|
||||
`master_key_rotation_in_progress`. После `promote` активный epoch меняется
|
||||
атомарно; процессы должны быть перезапущены с target key. Старый key после
|
||||
этого не проходит readiness.
|
||||
|
||||
Если ошибка возникла до promotion:
|
||||
|
||||
```bash
|
||||
crank-migrate master-key abort --rotation-id master-key-e1-to-e2
|
||||
```
|
||||
|
||||
Abort оставляет текущий epoch активным и очищает staged target ciphertext.
|
||||
`backup_ref` — только opaque ссылка на внешний защищённый backup; Crank не
|
||||
хранит key bytes в product backup set.
|
||||
|
||||
## Профили авторизации
|
||||
|
||||
@@ -29,5 +123,6 @@ Crank может вызывать REST API без авторизации или
|
||||
- Не вставляйте токены в статические заголовки операции.
|
||||
- Используйте секреты и профили авторизации для всех чувствительных данных.
|
||||
- Ротируйте секрет при подозрении на утечку.
|
||||
- Ротируйте `CRANK_MASTER_KEY` только через `crank-migrate master-key`; не
|
||||
меняйте значение в `.env` без preflight/rotate/verify/promote.
|
||||
- Не экспортируйте реальные секреты вместе с YAML-конфигурациями.
|
||||
|
||||
|
||||
@@ -19,6 +19,10 @@
|
||||
|
||||
Черновик можно редактировать и тестировать. MCP-клиенты видят только опубликованные операции, которые привязаны к опубликованному агенту.
|
||||
|
||||
Опубликованная версия неизменяема. Следующее сохранение создаёт новую Draft revision и не меняет уже опубликованный Agent catalog. Каталог позволяет удалить только never-published Draft; для опубликованной Operation доступна archive с подтверждением. Archive запрещает новые изменения и привязки, но сохраняет существующие Agent snapshots и историю.
|
||||
|
||||
UI использует `ETag` для защиты от stale вкладок. При конфликте локальные поля не перезаписываются: интерфейс предлагает перезагрузить authoritative Draft. Test run показывает безопасные Request ID и Trace ID и позволяет копировать их отдельно от payload.
|
||||
|
||||
### Создание операции
|
||||
|
||||
В мастере операции основной сценарий такой:
|
||||
@@ -35,6 +39,8 @@
|
||||
|
||||
Для обычной работы достаточно визуального конструктора связей. YAML/JSONPath открыт в блоке **Дополнительно** для сложных случаев: вложенные поля, ручная правка, перенос готовой конфигурации.
|
||||
|
||||
Portable YAML export использует format v2 и не содержит внутренних ID, lifecycle timestamps, samples, wizard metadata или credentials. Импорт одинакового document является no-op; изменённый upsert добавляет следующую Draft revision и требует актуальный state token.
|
||||
|
||||
Если пример ответа содержит массив, визуальное дерево показывает поля первого элемента как `items[0].name`. Это удобно, когда агенту нужно одно конкретное поле. Если агенту нужен весь список, используйте блок **Дополнительно** и верните массив целиком.
|
||||
|
||||
### Импорт OpenAPI
|
||||
|
||||
Reference in New Issue
Block a user