Files
crank/docs/migrations.md
T
bsodfather 10641faf43
CI / Rust Checks (push) Failing after 3m20s
CI / UI Checks (push) Has been skipped
CI / Frontend E2E (push) Has been skipped
CI / Community Image Smoke (push) Has been skipped
CI / Deploy (push) Has been skipped
fix(migrations): publish canonical v13 contract
2026-08-29 21:34:23 +03:00

174 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Миграции 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>` на:
```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: `<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":13}`.
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`](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 <!-- 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.
- 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.