# Миграции PostgreSQL Crank использует единственную append-only migration authority в `crank-registry`. `admin-api` и `mcp-server` выполняют только read-only preflight. DDL применяет one-shot binary `crank-migrate`, который Community Compose запускает до readiness сервисов. ## Безопасная последовательность оператора Для source Compose замените `` на: ```bash docker compose -f deploy/community/docker-compose.yml --env-file deploy/community/.env.example ``` Для image Compose используйте `docker compose -f deploy/community/docker-compose.images.yml --env-file .env`. Перед обновлением уже работающей установки выполните строго: 1. Read-only preflight: ` run --rm migrate crank-migrate preflight`. Exit `0` означает current, exit `2` — ожидаемую `migration_required`; exit `1` запрещает mutation до устранения причины. 2. Создайте и проверьте согласованный backup PostgreSQL и artifact storage. Для первой пустой установки зафиксируйте, что восстанавливать нечего. 3. Проверьте immutable plan: `cargo run -p admin-api --bin crank-migrate -- plan --check` в source checkout либо ` run --rm migrate crank-migrate plan` для образа. 4. Примените sequence: ` run --rm migrate crank-migrate apply`. 5. Повторите preflight и убедитесь в `{"status":"current","version":13}`. 6. Только теперь запускайте long-running services: ` up -d`. Обычный `up` также содержит обязательный migration job, но при upgrade он не заменяет предварительные preflight и backup. Migrator делает до десяти bounded попыток подключения с секундной паузой и затем безопасно завершается ошибкой. Команда `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 | Источник | Исторический владелец/lock | Известный контракт | |---|---|---| | `__crank_core_migrations` | `crank-registry`, session lock `0x4352414e4b` | columns `version, description, checksum, applied_at`; v1 checksum `crank-community-baseline-v1` | | `__crank_mcp_migrations` | MCP session store, session lock `0x4352414e4b4d4350` | columns `version, checksum, applied_at`; v1 checksum `mcp-transport-sessions-v1` | | `__crank_ext_migrations` | прежний `RegistryExtension`, без общего lock | legacy columns `extension_name, version, applied_at`; checksum отсутствовал | | `__crank_migrations` | canonical `MigrationAuthority`, transaction lock `0x4352414e4b4d4947` | append-only sequence с version/name/checksum/phase/compatibility | | `__crank_migration_legacy_audit` | canonical `MigrationAuthority` | только доказуемо сопоставленное legacy provenance | 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) и проверяется командой: ```bash 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 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. - V12 — global immutable artifact blob metadata и workspace-owned source relations. Digest/ref и size фиксируются canonical constraints; MIME, sensitivity и source lifecycle принадлежат scoped relation. Claim token/expiry зарезервированы bounded all-or-none contract для последующего reconciliation, но эта версия не запускает cleanup и не удаляет physical blobs. - V13 — expand-only indexes, поддерживающие bounded artifact/import cleanup и reconciliation ранее зарезервированных claim token/expiry; эта версия сама не выполняет cleanup, не переписывает данные и не удаляет physical blobs. - Каждая версия имеет 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. Legacy ledgers остаются readable. Down migration, destructive automatic rollback, ledger rewrite, `--force` и arbitrary SQL/path input отсутствуют. Полная N/N-1 upgrade/rollback qualification остаётся Story 7.2. ## Диагностика и восстановление CLI/stderr возвращают bounded JSON: `code`, `stage`, nullable numeric `version`, `recovery`. Database URL, credentials, raw SQL/driver body, row values и host paths не выводятся. | Code | Значение | Безопасное действие | |---|---|---| | `schema_missing` | Service startup увидел пустую schema | Запустить controlled migration; не разрешать runtime DDL | | `migration_required` | Schema отстаёт; preflight CLI завершает работу с exit `2` | Проверить backup и выполнить controlled `apply` | | `checksum_mismatch` | Artifact и ledger не совпали | Сначала проверить immutable application image/release manifest; восстанавливать БД только после доказанной ledger corruption | | `partial_sequence` | Relation/ledger/structural fingerprint неполон | Не чинить вручную; сопоставить backup и matching artifact | | `future_version` | База новее приложения | Установить matching application; не откатывать schema автоматически | | `legacy_conflict` | Legacy provenance невозможно доказать | Сохранить backup и привлечь оператора | | `lock_timeout` | Другой migration runner удерживает canonical lock | Дождаться завершения и повторить preflight | | `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`, `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. ## Правила разработчика - DDL, SQL migration assets и canonical advisory lock разрешены только в `crank-registry::migrations`. - Schema развивается `expand → bounded/resumable migrate → evidence-gated contract`. - Опубликованные source bytes/checksums не редактируются: добавляется новая version. - Для каждой version обязательны fresh/current/concurrent/corrupt/rollback/data-preservation tests и explicit Community scope scan.