feat: complete Epic 1 production foundation

This commit is contained in:
2026-08-25 01:24:11 +03:00
parent 767428436d
commit 182bde8ac0
298 changed files with 35719 additions and 5299 deletions
+1
View File
@@ -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
View File
@@ -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-ошибки с человекочитаемым сообщением и контекстом, если он доступен.
+6 -6
View File
@@ -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
}
+8 -1
View File
@@ -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"
}
]
}
+79 -12
View File
@@ -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
View File
@@ -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
View File
@@ -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-11SM-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
View File
@@ -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.
+24
View File
@@ -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).
+37
View File
@@ -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 и этой границей не считается завершённой.
+57 -7
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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.
## Для чего это нужно
+36
View File
@@ -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`
+14 -3
View File
@@ -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
View File
@@ -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
```
+3 -3
View File
@@ -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},
+181 -1
View File
@@ -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": {} } } } }
}
}
}
+39 -6
View File
@@ -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": []
},
{
+97 -2
View File
@@ -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-конфигурациями.
+6
View File
@@ -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