Files
crank/docs/migrations.md
T

14 KiB
Raw Blame History

Миграции 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 замените <compose> на:

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: <compose> 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 либо <compose> run --rm migrate crank-migrate plan для образа.
  4. Примените sequence: <compose> run --rm migrate crank-migrate apply.
  5. Повторите preflight и убедитесь в {"status":"current","version":11}.
  6. Только теперь запускайте long-running services: <compose> 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 и проверяется командой:

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.
  • Каждая версия имеет 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
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:

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 передаётся только через локальные файлы:

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.