174 lines
15 KiB
Markdown
174 lines
15 KiB
Markdown
# Миграции 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.
|