krona-staff/docs/TENANT-REHOME.md

6.8 KiB
Raw Blame History

Перенос арендатора Staff

scripts/rehome-tenant.mjs переносит все строки из одного tenant ID в другой в схеме public. Скрипт рассчитан на остановленное приложение и запускается только привилегированной ролью БД, способной обходить RLS и изменять таблицы. Строки пароля или подключения в отчёты не выводятся.

CLI по умолчанию выполняет только read-only dry-run; --dry-run можно указать явно. Совместимые имена --from/--to и --json принимаются для rehearsal-обвязок; вывод всегда JSON. Сначала на восстановленной копии базы выполните dry-run:

$env:DATABASE_ADMIN_URL = 'postgresql://…'
node scripts/rehome-tenant.mjs --from <ULID> --to <ULID> --dry-run --json

Для совместимости со старыми локальными инструкциями также принимаются DATABASE_REHOME_URL и DATABASE_MIGRATION_URL как fallback. --apply по-прежнему требует явный флаг, actor и проверенный --backup-evidence; aliases и default dry-run не разрешают запись сами по себе.

Dry-run открывает транзакцию READ ONLY и выводит только идентификаторы переноса и количество строк по таблицам. Он не блокирует записи; поэтому результат является снимком для планирования, а не гарантией актуальности на момент apply. На копии и в согласованное окно с остановленным приложением повторите dry-run непосредственно перед переносом.

Обязательное подтверждение восстановления

Apply требует --backup-evidence <путь> и читает JSON отчёт. Само имя файла и существование файла не считаются доказательством. JSON должен содержать сведения о зашифрованном off-host pg_dump -Fc и успешном restore drill именно этого backup:

{
  "backup": {
    "format": "pg_dump -Fc",
    "encrypted": true,
    "offHost": true,
    "sha256": "<64 hex characters>",
    "createdAt": "2026-09-26T08:00:00Z"
  },
  "restoreDrill": {
    "status": "passed",
    "backupSha256": "<same 64 hex characters>",
    "restoredAt": "2026-09-26T08:30:00Z",
    "verifiedAt": "2026-09-26T08:45:00Z",
    "isolatedDatabase": "krona_staff_restore_check"
  }
}

Этот файл удостоверяет результаты, предоставленные оператором; скрипт не проверяет доступ к хранилищу, шифрование или пригодность backup самостоятельно. Сохраните исходный backup, хеш и протокол restore drill вне сервера приложения.

Apply

Проверьте dry-run и backup evidence, остановите приложение, затем запускайте в согласованное окно. --actor — ULID уполномоченного пользователя Core, не пароль или секрет:

node scripts/rehome-tenant.mjs --apply `
  --source <ULID> --target <ULID> --actor <CORE_USER_ULID> `
  --backup-evidence .\evidence\restore-drill.json

Apply использует одну транзакцию, сериализуемый уровень изоляции, advisory lock и детерминированные ACCESS EXCLUSIVE locks всех таблиц public.staff_*. Скрипт отказывает, если целевой tenant уже содержит строки, таблица лишена tenant_id или FORCE RLS, обнаружен неизвестный пользовательский trigger либо внешний FK, или роль БД не может безопасно выполнить перенос. Существующие FK сохраняются с их определениями и состоянием validation; если проверка после пересоздания не совпала, транзакция откатывается.

Отчёт содержит количества перенесённых строк и суммы staff_payroll_ledger.amount_minor отдельно по валюте. В staff_audit_log перенос сам создаёт ровно одну новую запись tenant.rehomed под целевым tenant. Поэтому число строк staff_audit_log после commit равно исходному числу строк источника плюс одна; по всем остальным таблицам количество равно исходному. Запись аудита включает from/to, исходные количества, суммы реестра, Core actor, correlation ID и SHA-256/время backup и проверки restore.

При ошибке stdout/stderr не содержит URL подключения, значений строк или текста серверной ошибки: транзакция откатывается, а в stderr печатается только безопасное сообщение и, если есть, пятисимвольный PostgreSQL SQLSTATE. После успешного commit отдельно проверьте SELECT DISTINCT tenant_id по каждой staff таблице, количества строк, сумму реестра по валюте и наличие записи tenant.rehomed.

Интеграционный тест

Тест tests/integration/rehome-tenant.test.ts предназначен только для отдельной пустой PostgreSQL 17 базы, имя которой заканчивается на _rehome_test. Он требует DATABASE_REHOME_TEST_URL и явное DATABASE_REHOME_TEST_CONFIRM=I_ACKNOWLEDGE_DISPOSABLE_PG17_DATABASE, создаёт в ней роль приложения без пароля при необходимости и применяет миграции на чистой схеме. В конце тест очищает таблицы public.staff_*; проверка пустоты выполняется до вставки фикстур. Не указывайте production, pilot или общую базу.