Compare commits

...

8 commits

Author SHA1 Message Date
bf80b5040e docs(e2e): REPORT/NOTES — d52ecf3, b022885 и исправление сценария
Почему: таблица приёмки и разбор C6 должны соответствовать текущим SHA
веток Codex, а владелец должен видеть, какие расхождения с C6 остались
после d52ecf3 и какая часть красного была ошибкой сценария (типы счётчиков,
d477dde), а не продукта.

- REPORT: таблица по повторному прогону (b022885, d52ecf3), разбор d52ecf3.
- NOTES C6: что закрыл d52ecf3; открыто — многострочный JSON, сумма журнала
  в dry-run, dry-run по умолчанию и обязательные --apply/--actor/--backup-evidence.
- NOTES разд. 8: K18 на b022885 — ротация на ПК есть, на сервере нет,
  личные пути в новых файлах.
2026-09-26 17:20:23 +07:00
75f76430d4 docs(e2e): доказательства — повторная приёмка после d52ecf3 и b022885
Почему: пока шёл разбор, Codex запушил codex/fix-tenant-rehome d52ecf3
(17:02, синонимы C6) и codex/fix-pilot-backups b022885 (17:07, ротация на ПК).
Таблица по старым SHA устарела, поэтому все 7 веток прогнаны заново
(стенд a6a1b5e): round3-accept-f7d3c73-rerun.md.

В round3-k18-and-rehome.md, разд. 6: вывод [PILOT-REHOME] на d52ecf3 как есть
(многострочный JSON → «dry-run без JSON-отчёта»), пошаговая диагностика
в копии сценария, которую потом откатил (что упадёт дальше: сумма журнала
в dry-run, dry-run по умолчанию вместо переноса), и тесты backup на b022885.
2026-09-26 17:20:23 +07:00
d477dde387 fix(e2e): PILOT-REHOME сравнивает счётчики dry-run как числа
Почему это ошибка сценария, а не подгонка под продукт: C6 не фиксирует тип
счётчиков, count(*) из PostgreSQL приходит в node-postgres строкой (bigint),
а сам сценарий уже фильтровал `Number(n) > 0` и сравнивал сумму журнала
через String(). Строгое сравнение '2' с 2 роняло проверку при верных числах
(диагностика на codex/fix-tenant-rehome d52ecf3: `expected { staff_attendance_events:
'2', …(26) } to deeply equal { staff_attendance_events: 2, …(26) }`).

Остальные проверки не ослаблены: JSON последней строкой, сумма журнала
в dry-run, перенос без флага и все сверки после переноса — как в C6.
На основе f7d3c73 [PILOT-REHOME] по-прежнему красный (скрипта нет),
run-e2e: `Tests 46 failed | 10 passed (56)`, `[run-e2e] OK`.
2026-09-26 17:19:47 +07:00
a6a1b5e937 docs(e2e): NOTES — расхождение c73bb3b с C6, круг 3б
Зачем: Codex и владельцу нужен точный список отличий интерфейса
scripts/rehome-tenant.mjs (c73bb3b) от контракта C6, чтобы выбрать один
интерфейс, а не подгонять сценарий под продукт молча.

- C6: таблица отличий (флаги --source/--target, обязательный --apply
  с --actor/--backup-evidence, DATABASE_REHOME_URL, нет --json,
  многострочный JSON, нет суммы журнала в dry-run) и два варианта решения.
- Раздел 8: tenant-config (как закрыть — по ACCEPTANCE-3), rehome
  (интеграционный тест в check/CI всегда skipped и требует суперпользователя),
  K18 (нет globals, isolation.sql зашит на ULID, личный путь в доке,
  ротации нет, тесты backup не в CI).
2026-09-26 17:13:45 +07:00
404365c644 docs(e2e): REPORT — круг 3б, приёмка на основе f7d3c73
Зачем: владелец ждёт по ACCEPTANCE-3 таблицу приёмки всех открытых веток
на новой основе, включая три новые ветки Codex, и ответ, зеленеет ли
[PILOT-REHOME] на codex/fix-tenant-rehome.

- Таблица accept-branch по 7 веткам (стенд 18f944e); храповик не тронут,
  потому что merge-base --is-ancestor показывает, что ничего не влито.
- fix-tenant-config: вывод владельца подтверждён (sync.test.ts:556).
- fix-tenant-rehome: [PILOT-REHOME] красный из-за интерфейса
  (Unknown option --from); по существу перенос проверен на копии.
  Сценарий не менялся: ошибок в нём нет.
- fix-pilot-backups (K18): тесты с моками зелёные, учебное восстановление
  без Docker прошло, без globals в кластер без ролей не восстанавливается.
- «Не проверено»: то, что требует Docker/systemd/ssh и данных пилота.
2026-09-26 17:13:45 +07:00
e33e593c99 docs(e2e): K18 — восстановление в кластер без ролей
Почему: backup.sh снимает только pg_dump -Fc одной БД, без ролей (globals). Проверено на
временном кластере PG17 без ролей krona_*: pg_restore exit=1, 61 ошибка «role krona_migrator does
not exist», данные есть, владелец — postgres. В пилоте роли, вероятно, создаются при инициализации
контейнера (POSTGRES_USER, docker/postgres/init-app-role.sh) — без Docker не проверено.
2026-09-26 17:12:38 +07:00
43b684e04f docs(e2e): доказательства — приёмка на f7d3c73, K18 и перенос арендатора
Почему: владелец просил прогнать все открытые ветки против новой основы f7d3c73, включая
codex/fix-tenant-config, codex/fix-tenant-rehome, codex/fix-pilot-backups, и проверить K18 и
scripts/rehome-tenant.mjs фактами.
round3-accept-f7d3c73.md — вывод accept-branch (7 веток, 28 пар merge-tree — конфликтов нет).
round3-k18-and-rehome.md — тесты Codex для backup; учебное восстановление без Docker (шим docker →
локальный pg_dump PG17, CMS-шифрование, sha256, pg_restore, restore-drill-report.sql совпал);
перенос заглушки на восстановленной копии интерфейсом продукта; [PILOT-REHOME] через
диагностический адаптер (не в продукте, не в сценарии) — исходники скриптов приложены.
2026-09-26 17:12:07 +07:00
18f944e3c3 test(accept): одноразовая БД *_rehome_test для rehome-tenant.test.ts
Почему: tests/integration/rehome-tenant.test.ts (codex/fix-tenant-rehome) выполняется только
при DATABASE_REHOME_TEST_URL на пустую БД с именем *_rehome_test под суперпользователем и
DATABASE_REHOME_TEST_CONFIRM (docs/TENANT-REHOME.md); без них файл пропускается целиком и
приёмка ничего не проверяет. В admin-режиме accept-branch создаёт пустую krona_acc_rehome_test
только для файлов, которые читают DATABASE_REHOME_TEST_URL.
Проверено: accept-branch --skip-check --skip-e2e codex/fix-tenant-rehome →
rehome-tenant.test.ts | OK | 1 passed (1) | 1 passed (1).
2026-09-26 16:59:10 +07:00
7 changed files with 740 additions and 4 deletions

View file

@ -23,6 +23,8 @@
// Интеграционная БД в этом режиме заведомо изолирована, поэтому для тестов с «предохранителем» // Интеграционная БД в этом режиме заведомо изолирована, поэтому для тестов с «предохранителем»
// (codex/fix-live-database: DATABASE_TEST_ISOLATED, DATABASE_TEST_GUARD_TOKEN, таблица // (codex/fix-live-database: DATABASE_TEST_ISOLATED, DATABASE_TEST_GUARD_TOKEN, таблица
// krona_staff_test_guard, имя БД krona_staff_audit_*) он выставляется со случайным маркером. // krona_staff_test_guard, имя БД krona_staff_audit_*) он выставляется со случайным маркером.
// Для tests/integration/rehome-tenant.test.ts (codex/fix-tenant-rehome) — отдельная пустая БД
// krona_acc_rehome_test под суперпользователем и DATABASE_REHOME_TEST_CONFIRM (docs/TENANT-REHOME.md).
// Файл, где все тесты пропущены, — отдельный вердикт SKIP (ничего не проверил). // Файл, где все тесты пропущены, — отдельный вердикт SKIP (ничего не проверил).
// Если заданы E2E_MIGRATOR_URL и E2E_APP_URL — роли не создаются и пароли не меняются, // Если заданы E2E_MIGRATOR_URL и E2E_APP_URL — роли не создаются и пароли не меняются,
// все прогоны идут последовательно в БД из этих URL со сбросом через откат всех миграций. // все прогоны идут последовательно в БД из этих URL со сбросом через откат всех миграций.
@ -318,6 +320,19 @@ async function createGuard(db) {
return marker; return marker;
} }
/**
* Отдельная пустая одноразовая БД для tests/integration/rehome-tenant.test.ts (codex/fix-tenant-rehome,
* docs/TENANT-REHOME.md): имя на `_rehome_test`, суперпользователь, явное подтверждение.
*/
async function rehomeTestDatabase() {
const name = 'krona_acc_rehome_test';
const db = await freshDatabase(name);
return {
DATABASE_REHOME_TEST_URL: db.admin,
DATABASE_REHOME_TEST_CONFIRM: 'I_ACKNOWLEDGE_DISPOSABLE_PG17_DATABASE',
};
}
async function stepIntegration(target, wt) { async function stepIntegration(target, wt) {
const dir = path.join(wt.dir, 'tests/integration'); const dir = path.join(wt.dir, 'tests/integration');
const files = existsSync(dir) const files = existsSync(dir)
@ -332,6 +347,12 @@ async function stepIntegration(target, wt) {
const db = await freshDatabase('krona_staff_audit_acc_it'); const db = await freshDatabase('krona_staff_audit_acc_it');
const m = await migrate(wt, db, path.join(logDir, `${slug}-migrate.txt`)); const m = await migrate(wt, db, path.join(logDir, `${slug}-migrate.txt`));
const guard = m.code === 0 && dbMode === 'admin' ? await createGuard(db) : null; const guard = m.code === 0 && dbMode === 'admin' ? await createGuard(db) : null;
const rehomeEnv =
m.code === 0 &&
dbMode === 'admin' &&
readFileSync(path.join(dir, file), 'utf8').includes('DATABASE_REHOME_TEST_URL')
? await rehomeTestDatabase()
: {};
if (m.code !== 0) { if (m.code !== 0) {
results.push({ results.push({
file, file,
@ -352,6 +373,7 @@ async function stepIntegration(target, wt) {
DATABASE_TEST_MIGRATION_URL: db.migrator, DATABASE_TEST_MIGRATION_URL: db.migrator,
DATABASE_MIGRATION_URL: db.migrator, DATABASE_MIGRATION_URL: db.migrator,
...(guard ? { DATABASE_TEST_ISOLATED: 'true', DATABASE_TEST_GUARD_TOKEN: guard } : {}), ...(guard ? { DATABASE_TEST_ISOLATED: 'true', DATABASE_TEST_GUARD_TOKEN: guard } : {}),
...rehomeEnv,
}), }),
logFile: path.join(logDir, `${slug}.txt`), logFile: path.join(logDir, `${slug}.txt`),
}, },

View file

@ -466,8 +466,12 @@ describe('PILOT-REHOME: перенос заглушки арендатора н
const dry = runRehome(['--dry-run', '--json'], adminUrl); const dry = runRehome(['--dry-run', '--json'], adminUrl);
expect(dry.status, `dry-run exit: ${dry.stderr}`).toBe(0); expect(dry.status, `dry-run exit: ${dry.stderr}`).toBe(0);
expect(dry.report, `dry-run без JSON-отчёта: ${dry.stdout}`).not.toBeNull(); expect(dry.report, `dry-run без JSON-отчёта: ${dry.stdout}`).not.toBeNull();
// Счётчики сравниваются как числа: count(*) из pg приходит строкой (bigint), C6 тип не фиксирует,
// а фильтр `Number(n) > 0` и сравнение суммы журнала и раньше допускали строку.
const dryTables = Object.fromEntries( const dryTables = Object.fromEntries(
Object.entries(dry.report!.tables ?? {}).filter(([, n]) => Number(n) > 0), Object.entries(dry.report!.tables ?? {})
.map(([table, n]) => [table, Number(n)] as const)
.filter(([, n]) => n > 0),
); );
expect(dryTables).toEqual(expectedTables); expect(dryTables).toEqual(expectedTables);
expect(String(dry.report!.ledger_amount_minor_sum)).toBe(before!.stub.ledgerSum); expect(String(dry.report!.ledger_amount_minor_sum)).toBe(before!.stub.ledgerSum);

View file

@ -93,7 +93,8 @@ ID в названиях тестов — двойные «владелец/мо
- Запуск: `DATABASE_ADMIN_URL=<админ БД> node scripts/rehome-tenant.mjs --from 01J00000000000000000000000 --to 01M2R6AC5652NPT3MG34DKN4ZP [--dry-run] [--json]`. - Запуск: `DATABASE_ADMIN_URL=<админ БД> node scripts/rehome-tenant.mjs --from 01J00000000000000000000000 --to 01M2R6AC5652NPT3MG34DKN4ZP [--dry-run] [--json]`.
Стенд передаёт `DATABASE_ADMIN_URL` суперпользователя в БД стенда (`E2E_DB_ADMIN_URL` или Стенд передаёт `DATABASE_ADMIN_URL` суперпользователя в БД стенда (`E2E_DB_ADMIN_URL` или
`E2E_PG_ADMIN_URL` с именем БД). `E2E_PG_ADMIN_URL` с именем БД).
- `--json`: последней строкой stdout — `{"tables":{"<таблица>":<строк у --from>},"ledger_amount_minor_sum":<число или строка>}`. - `--json`: последней строкой stdout — `{"tables":{"<таблица>":<строк у --from>},"ledger_amount_minor_sum":<число или строка>}`
(счётчики — число или строка с числом).
Таблицы с нулём можно не печатать. Таблицы с нулём можно не печатать.
- `--dry-run`: exit 0, те же числа, в БД ничего не меняется. - `--dry-run`: exit 0, те же числа, в БД ничего не меняется.
- Перенос: exit 0; под `--from` 0 строк во **всех** таблицах с `tenant_id` (список из - Перенос: exit 0; под `--from` 0 строк во **всех** таблицах с `tenant_id` (список из
@ -116,6 +117,35 @@ ID в названиях тестов — двойные «владелец/мо
`staff_timesheet_entries` требуют `actor_id/created_by/updated_by = current_setting('app.user_id')` — `staff_timesheet_entries` требуют `actor_id/created_by/updated_by = current_setting('app.user_id')` —
для записи аудита без обхода RLS выставить `app.user_id`. для записи аудита без обхода RLS выставить `app.user_id`.
**Расхождение с `codex/fix-tenant-rehome` (c73bb3b, 16:48 NOVT; ACCEPTANCE-3 с просьбой держаться C6 — 16:35).**
Сценарий на ветке: `dry-run exit: Tenant rehome stopped. Unknown option --from.` По существу скрипт делает
всё, что проверяет сценарий (см. REPORT «Круг 3б»); не совпадает только интерфейс:
| C6 (сценарий) | c73bb3b | Итог в сценарии |
| ------------------------------------------- | ----------------------------------------------------------------------- | -------------------------------------------------- |
| `--from`, `--to` | `--source`, `--target` | `Unknown option --from`, exit 1 |
| без флага — перенос | нужен `--apply`, без режима — `Specify --dry-run or --apply.` | — |
| — | `--apply` требует `--actor <ULID>` и `--backup-evidence <json>` | у сценария нет ни того, ни другого |
| `--json` | неизвестный флаг → exit 1; JSON печатается всегда | — |
| `DATABASE_ADMIN_URL` | `DATABASE_REHOME_URL` (запасной вариант `DATABASE_MIGRATION_URL`) | — |
| одна строка JSON, числа | многострочный JSON (`JSON.stringify(…, null, 2)`), счётчики — строки | парсер сценария ищет JSON в последней строке |
| `ledger_amount_minor_sum` в dry-run и apply | в dry-run суммы нет; в apply `payrollLedger: [{currency, amountMinor}]` | с адаптером: `expected 'undefined' to be '328228'` |
| `tables` — строки у `--from` | в apply `tables` — у `--to` после переноса (`staff_audit_log` +1) | не проверяется |
**d52ecf3 (17:02)** закрыл строки 1, 4, 5 таблицы: `--from/--to`, `--json`, `DATABASE_ADMIN_URL` принимаются. Без
флага теперь **dry-run** (по C6 — перенос), так что запуск сценария без `--apply` не переносит данные. Открытыми
остаются многострочный JSON (сценарий: `dry-run без JSON-отчёта`), сумма журнала в dry-run и обязательные
`--apply --actor --backup-evidence` (разд. 6.3 в `report-evidence/round3-k18-and-rehome.md`). Типы счётчиков
расходились из-за ошибки сценария; она исправлена в d477dde, сравнение идёт через `Number()`.
Какой интерфейс оставить, решает владелец. Варианты: (а) Codex добавляет в скрипт C6-совместимые
`--from/--to/--json`, `DATABASE_ADMIN_URL`, сумму журнала в dry-run, а `--actor`/`--backup-evidence`
берёт из переменных окружения (например, `REHOME_ACTOR_ID`, `REHOME_BACKUP_EVIDENCE`); (б) владелец утверждает
интерфейс c73bb3b — тогда я меняю сценарий под него отдельным коммитом, а сумму журнала в dry-run
оставляю требованием (TASK-GROK-3 §4 п.3: «`--dry-run` — ничего не меняет, печатает те же числа», то есть числа п.2, включая `SUM(amount_minor)`). В TASK-CODEX-2 §2.2 п.4 dry-run
описан как «только отчёт о количестве строк по таблицам», то есть по этому пункту c73bb3b задание
выполняет, а расходится только с C6.
## 2. Дефекты продукта, найденные сценариями (все красные сейчас) ## 2. Дефекты продукта, найденные сценариями (все красные сейчас)
Команда и фактический вывод — `tests/e2e/evidence/e2e-run.txt`; таблица — `tests/e2e/REPORT.md`. Команда и фактический вывод — `tests/e2e/evidence/e2e-run.txt`; таблица — `tests/e2e/REPORT.md`.
@ -251,3 +281,35 @@ does not exist` в `beforeAll` (`Tests 2 skipped`). На `6c5bf36`/`b283736` и
ссылаться на событие outbox другого арендатора (RLS фильтрует чтение, но не ссылку). Предложение: ссылаться на событие outbox другого арендатора (RLS фильтрует чтение, но не ссылку). Предложение:
`UNIQUE (tenant_id, id)` на `staff_outbox` и FK `(tenant_id, outbox_id)`. `UNIQUE (tenant_id, id)` на `staff_outbox` и FK `(tenant_id, outbox_id)`.
- **PILOT-REHOME**: скрипта нет → `[PILOT-REHOME]` красный; контракт — C6. - **PILOT-REHOME**: скрипта нет → `[PILOT-REHOME]` красный; контракт — C6.
## 8. Круг 3б (основа f7d3c73, 2026-09-26 ~17:00 NOVT)
Вывод — `tests/e2e/report-evidence/round3-accept-f7d3c73.md`, `round3-k18-and-rehome.md`.
- **codex/fix-tenant-config (535052e)**: `npm run check` красный —
`tests/unit/sync.test.ts:556:18` → `STANDALONE_TENANT_ID uses the development placeholder…`
(строка 553 — заглушка, 563 — `NODE_ENV: 'test'`). Вывод владельца подтверждён; как исправить —
ACCEPTANCE-3 (разрешить заглушку при `NODE_ENV === 'test'`).
- **codex/fix-tenant-rehome (c73bb3b → d52ecf3)**: по существу перенос верный (копия после восстановления: счётчики,
ledger, FK, аудит `tenant.rehomed`, изоляция, отказ повтора). `[PILOT-REHOME]` красный из-за интерфейса —
таблица различий в C6. Интеграционный тест `rehome-tenant.test.ts` зелёный только с
`DATABASE_REHOME_TEST_URL`/`DATABASE_REHOME_TEST_CONFIRM` — в `npm run check` и CI он всегда `skipped`.
Тест требует подключения суперпользователя (`rehome-tenant.test.ts:92-93`) и содержит один `it` —
запуск скрипта от не-суперпользователя (как `krona_migrator` в пилоте) не покрыт.
- **codex/fix-pilot-backups (a005c5e → b022885, K18)**: `test-backup.sh`/`test-backup-pull.sh` проходят (моки), но в
`npm run check` и CI не входят. Учебное восстановление без Docker прошло (шим docker → pg_dump PG17,
CMS, sha256, pg_restore, `restore-drill-report.sql` совпал). Замечания:
- `backup.sh` снимает только `pg_dump -Fc` одной БД: ролей (globals) в копии нет. В кластере без ролей
`pg_restore` → exit 1, 61 ошибка `role "krona_migrator" does not exist`. Шаг 4 runbook («Restore required
roles and grants») нужно сделать исполнимым: скрипт создания ролей для восстановления или
`pg_dumpall --globals-only --no-role-passwords` рядом с копией.
- `restore-drill-isolation.sql` жёстко зашит на `01J00000000000000000000000` → `01M2R6AC5652NPT3MG34DKN4ZP`;
после переноса «source/target» поменяются местами — стоит параметризовать (`psql -v`).
- В `docs/BACKUP-RESTORE.md` личный путь `C:/Users/trush/Documents/KronaStaffBackups/` — в репозитории
лучше указать роль или место без имени пользователя.
- b022885: ротация копий на ПК есть (`BACKUP_LOCAL_RETENTION_DAYS`, 90 дней; в `test-backup-pull.sh` есть проверка),
ротации на сервере нет. В новых `scripts/run-pilot-backup-pull.ps1` и `deploy/pilot/backup.home-server.example`
тоже зашиты личные пути. Контроль отказов таймера и задачи pull пока описан только в документе.
- Предложение (моя зона, после слияния): добавить в CI `bash scripts/test-backup.sh`,
`bash scripts/test-backup-pull.sh` и запуск `rehome-tenant.test.ts` / `live-postgres-remediation.test.ts`
на одноразовых БД — так же, как это делает `accept-branch`.

View file

@ -132,6 +132,75 @@ merge `grok/e2e-harness`. Вывод — [`round3-combined-merge.md`](report-evi
([`report-evidence/round3-pilot-rehome.txt`](report-evidence/round3-pilot-rehome.txt)). ([`report-evidence/round3-pilot-rehome.txt`](report-evidence/round3-pilot-rehome.txt)).
- Контракт, который проверяет сценарий, — `NOTES-FOR-EXECUTORS.md`, C6. - Контракт, который проверяет сценарий, — `NOTES-FOR-EXECUTORS.md`, C6.
### Круг 3б: основа `f7d3c73` (ACCEPTANCE-3), 16:59 и 17:14 NOVT
Храповик **не тронут**: после `git fetch` `git merge-base --is-ancestor origin/<ветка>
origin/codex/bootstrap-platform-foundation` — `not merged` для grok/e2e-harness, fix-live-database,
fix-format-baseline, fix-reject-device-shifts, fix-sync-c0-bootstrap, fix-tenant-config,
fix-tenant-rehome, fix-pilot-backups.
Команда: `node scripts/test/accept-branch.mjs <7 веток, не являющихся предками основы>`. Первый прогон (16:59, стенд
`18f944e`) — [`round3-accept-f7d3c73.md`](report-evidence/round3-accept-f7d3c73.md). Пока шёл разбор, Codex
запушил `fix-tenant-rehome` d52ecf3 (17:02) и `fix-pilot-backups` b022885 (17:07), поэтому все 7 веток прогнаны
повторно (17:14, стенд `a6a1b5e`) — [`round3-accept-f7d3c73-rerun.md`](report-evidence/round3-accept-f7d3c73-rerun.md).
В таблице — повторный прогон; на первом прогоне по c73bb3b и a005c5e результаты те же.
| Ветка | SHA | check | integration | e2e red/green | Стало зелёным | Стало красным |
| ------------------------------------ | ------- | ------------------------------------------------------------------------ | ----------------------------------------------------- | ------------- | ------------------------------- | ------------- |
| основа | f7d3c73 | OK (195 passed, 1 skipped) | 3/4, FAIL tenant-isolation | 46/5 | n/a | n/a |
| antigravity/fix-reject-device-shifts | b283736 | OK | 4/5, FAIL tenant-isolation | 44/7 | K13/SYNC-A, K13/SYNC-B | — |
| antigravity/fix-sync-c0-bootstrap | 24cc18d | OK | 5/6, FAIL tenant-isolation | 43/8 | K13/SYNC-A, K13/SYNC-B, SYNC-C0 | — |
| codex/fix-format-baseline | a05777c | OK | 3/4, FAIL tenant-isolation | 46/5 | — | — |
| codex/fix-live-database | 1573860 | OK | 4/5, FAIL tenant-isolation | 44/7 | K3/N2, K4/N1 | — |
| codex/fix-pilot-backups | b022885 | OK | 3/4, FAIL tenant-isolation | 46/5 | — | — |
| codex/fix-tenant-config | 535052e | **FAIL** `Error: STANDALONE_TENANT_ID uses the development placeholder…` | 3/4, FAIL tenant-isolation | 46/5 | — | — |
| codex/fix-tenant-rehome | d52ecf3 | OK | 4/5 (rehome-tenant.test.ts OK), FAIL tenant-isolation | 46/5 | — | — |
- `git merge-tree`: 28 пар (7 с основой + 21 между собой) — **все чисто**.
- «Форматирование без поведения» `a05777c`: `изменено файлов: 19 — ТОЛЬКО ФОРМАТИРОВАНИЕ`.
- `tenant-isolation.test.ts` — N5 (`permission denied for table staff_employees`), исправление в grok/e2e-harness.
- accept-branch дополнен (`18f944e`): для `rehome-tenant.test.ts` создаётся пустая `krona_acc_rehome_test`
(суперпользователь, `DATABASE_REHOME_TEST_CONFIRM` из `docs/TENANT-REHOME.md`) — иначе файл пропускается.
**codex/fix-tenant-config — вывод владельца подтверждён.** Лог `npm run check`:
`FAIL tests/unit/sync.test.ts [ tests/unit/sync.test.ts ]`, `Error: STANDALONE_TENANT_ID uses the development
placeholder, which is allowed only in development on loopback`, `❯ tests/unit/sync.test.ts:556:18`,
`Test Files 1 failed | 18 passed | 1 skipped (20)`. В ветке: строка 553 `const tenantId = '01J00000000000000000000000'`,
556 `loadConfig({`, 563 `NODE_ENV: 'test'`. (У владельца `1 failed | 17 passed`; у меня на один файл больше
из-за оверлея стенда — `tests/e2e/harness/node-commands.test.ts`.)
**codex/fix-tenant-rehome — `[PILOT-REHOME]` красный.** Вывод на ветке:
`dry-run exit: Tenant rehome stopped. Unknown option --from. … expected 1 to be +0`. Скрипт есть, но интерфейс
не совпадает с C6 (разница — NOTES, C6). По существу скрипт проверен двумя способами
([`round3-k18-and-rehome.md`](report-evidence/round3-k18-and-rehome.md)):
1. На восстановленной копии (после учебного восстановления K18), интерфейсом продукта: dry-run exit 0 и БД не изменилась;
apply без `--backup-evidence` → отказ; apply → exit 0, под заглушкой 0 строк, счётчики совпали, audit 2 → 3
(`tenant.rehomed tenant_rehome`), прочие арендаторы не изменились, ledger `RUB 263568` до и после, FK до и после
совпали (md5), `krona_staff_app`: 0 под заглушкой / 3 под новым tenant, `UPDATE staff_payroll_ledger` →
`permission denied`, повтор → `Target tenant already contains staff data; refusing merge`, exit 1.
2. Сценарий `[PILOT-REHOME]` через диагностический адаптер C6 → c73bb3b (не в продукте и не в сценарии):
вариант 1 (только перевод флагов и формата) — `expected 'undefined' to be '328228'`: **в dry-run нет суммы
журнала**; вариант 2 (сумму досчитывает адаптер) — `✓ [PILOT-REHOME]`. Других расхождений сценарий не нашёл.
На **d52ecf3** (17:02) Codex добавил синонимы `--from/--to`, `--json`, `DATABASE_ADMIN_URL` и сделал dry-run режимом
по умолчанию. `[PILOT-REHOME]` всё равно красный: `dry-run без JSON-отчёта: { "mode": "dry-run", … } : expected null
not to be null` — JSON печатается на нескольких строках, а C6 требует его последней строкой. Если снимать
расхождения по одному в копии сценария (не в коммите, [`round3-k18-and-rehome.md`](report-evidence/round3-k18-and-rehome.md), разд. 6.3), дальше
падают: счётчики-строки (`'2'` vs `2`) → **это ошибка сценария**, исправлена в `d477dde` (сравнение через `Number()`);
`expected 'undefined' to be '375888'` — в dry-run нет суммы журнала; `под заглушкой остались строки` — вызов без
флага теперь dry-run, а по C6 это перенос, для которого продукт требует `--apply --actor --backup-evidence`.
Других изменений в сценарии нет.
**codex/fix-pilot-backups (K18).** `bash scripts/test-backup.sh` и `bash scripts/test-backup-pull.sh` —
оба `passed`, exit 0 (моки docker/ssh/scp) и на a005c5e, и на b022885 (b022885 добавил ротацию копий на ПК, 90 дней,
и пользовательский systemd-сервис; ротации на сервере нет). В `npm run check` и CI они не входят. Учебное восстановление без
Docker (шим `docker compose exec` → локальный `pg_dump` PG17): `backup.sh` exit 0, на сервере 0 незашифрованных
файлов, `sha256sum -c` OK, расшифровка ключом вне сервера, `pg_restore --exit-on-error` exit 0,
`restore-drill-report.sql` на источнике и копии — `идентичны` (79 строк, `RLS|27|t`), isolation:
`source-employee-count|3`, `target-employee-count|0`. Восстановление в кластер без ролей: `pg_restore` exit 1,
61 ошибка `role "krona_migrator" does not exist` — в копии нет ролей (globals).
## Как запустить ## Как запустить
```sh ```sh
@ -365,7 +434,11 @@ $ git status --porcelain
`codex/fix-live-database`. `codex/fix-live-database`.
- S9 проверен удалением SQL-запросом мигратора (публичного API удаления точки нет). - S9 проверен удалением SQL-запросом мигратора (публичного API удаления точки нет).
- Причина невоспроизводимости N13 — гипотеза по коду. - Причина невоспроизводимости N13 — гипотеза по коду.
- Круг 3: `scripts/rehome-tenant.mjs` не существует — `[PILOT-REHOME]` проверен только временным - Круг 3: в основе `scripts/rehome-tenant.mjs` нет; на `codex/fix-tenant-rehome` скрипт проверен на копии
эталоном; контракт C6 — предположение. `accept-branch` в CI не запускается (разовый инструмент и через диагностический адаптер (интерфейс не совпадает с C6). `accept-branch` в CI не запускается (разовый инструмент
владельца/приёмщика). Режим `E2E_MIGRATOR_URL/E2E_APP_URL` проверен на том же локальном кластере владельца/приёмщика). Режим `E2E_MIGRATOR_URL/E2E_APP_URL` проверен на том же локальном кластере
(роли созданы заранее `provision-pg`), на чужом кластере — не проверено. (роли созданы заранее `provision-pg`), на чужом кластере — не проверено.
- Круг 3б (K18) без Docker не проверено: настоящий `docker compose exec` в пилотном контейнере, systemd
timer/service, `pull-pilot-backups.sh` с реальным ssh/scp (только моки из тестов Codex), восстановление
в контейнер `postgres` с `init-app-role.sh`, хранение ключа и политика хранения. Учебное восстановление Codex
на пилоте (sha256 `2fc680ae…`, 51 сотрудник) не перепроверялось — данных пилота у меня нет.

View file

@ -0,0 +1,95 @@
## accept-branch — 2026-09-26 10:17 UTC
База: `origin/codex/bootstrap-platform-foundation` = f7d3c73; стенд (оверлей tests/e2e, scripts/test): a6a1b5e; БД: роли созданы один раз через E2E_PG_ADMIN_URL (provision-pg), дальше --roles keep. Логи: `tests/e2e/evidence/accept/2026-09-26T10-14-42/`.
### Сводка
| Ветка | SHA | npm run check | integration (красные файлы) | e2e red/green | e2e: стало зелёным | e2e: стало красным |
| ------------------------------------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- | ------------------------------------------- | ------------------ | ------------------------------- |
| origin/codex/bootstrap-platform-foundation (база) | f7d3c73 | OK (195 passed | 1 skipped (196)) | 3/4 зелёных; FAIL: tenant-isolation.test.ts | 46/5 | n/a | n/a |
| origin/antigravity/fix-reject-device-shifts | b283736 | OK (196 passed | 3 skipped (199)) | 4/5 зелёных; FAIL: tenant-isolation.test.ts | 44/7 | K13/SYNC-A, K13/SYNC-B | — |
| origin/antigravity/fix-sync-c0-bootstrap | 24cc18d | OK (197 passed | 5 skipped (202)) | 5/6 зелёных; FAIL: tenant-isolation.test.ts | 43/8 | K13/SYNC-A, K13/SYNC-B, SYNC-C0 | — |
| origin/codex/fix-format-baseline | a05777c | OK (195 passed | 1 skipped (196)) | 3/4 зелёных; FAIL: tenant-isolation.test.ts | 46/5 | — | — |
| origin/codex/fix-live-database | 1573860 | OK (197 passed | 2 skipped (199)) | 4/5 зелёных; FAIL: tenant-isolation.test.ts | 44/7 | K3/N2, K4/N1 | — |
| origin/codex/fix-pilot-backups | b022885 | OK (195 passed | 1 skipped (196)) | 3/4 зелёных; FAIL: tenant-isolation.test.ts | 46/5 | — | — |
| origin/codex/fix-tenant-config | 535052e | FAIL exit=1: Error: STANDALONE_TENANT_ID uses the development placeholder, which is allowed only in development on loopback | 3/4 зелёных; FAIL: tenant-isolation.test.ts | 46/5 | — | — |
| origin/codex/fix-tenant-rehome | d52ecf3 | OK (198 passed | 2 skipped (200)) | 4/5 зелёных; FAIL: tenant-isolation.test.ts | 46/5 | — | — |
### Интеграционные тесты (каждый файл отдельно, свежая БД)
| Ветка | Файл | Итог | Test Files | Tests | Первая ошибка |
| ------------------------------------------- | --------------------------------- | ---- | ------------ | -------------- | -------------------------------------------------- |
| origin/codex/bootstrap-platform-foundation | health.test.ts | OK | 1 passed (1) | 3 passed (3) | |
| origin/codex/bootstrap-platform-foundation | openapi-runtime.test.ts | OK | 1 passed (1) | 1 passed (1) | |
| origin/codex/bootstrap-platform-foundation | portal.test.ts | OK | 1 passed (1) | 12 passed (12) | |
| origin/codex/bootstrap-platform-foundation | tenant-isolation.test.ts | FAIL | 1 failed (1) | 1 passed (1) | error: permission denied for table staff_employees |
| origin/antigravity/fix-reject-device-shifts | health.test.ts | OK | 1 passed (1) | 3 passed (3) | |
| origin/antigravity/fix-reject-device-shifts | openapi-runtime.test.ts | OK | 1 passed (1) | 1 passed (1) | |
| origin/antigravity/fix-reject-device-shifts | portal.test.ts | OK | 1 passed (1) | 12 passed (12) | |
| origin/antigravity/fix-reject-device-shifts | sync-rejection.test.ts | OK | 1 passed (1) | 2 passed (2) | |
| origin/antigravity/fix-reject-device-shifts | tenant-isolation.test.ts | FAIL | 1 failed (1) | 1 passed (1) | error: permission denied for table staff_employees |
| origin/antigravity/fix-sync-c0-bootstrap | health.test.ts | OK | 1 passed (1) | 3 passed (3) | |
| origin/antigravity/fix-sync-c0-bootstrap | openapi-runtime.test.ts | OK | 1 passed (1) | 1 passed (1) | |
| origin/antigravity/fix-sync-c0-bootstrap | portal.test.ts | OK | 1 passed (1) | 12 passed (12) | |
| origin/antigravity/fix-sync-c0-bootstrap | sync-bootstrap.test.ts | OK | 1 passed (1) | 2 passed (2) | |
| origin/antigravity/fix-sync-c0-bootstrap | sync-rejection.test.ts | OK | 1 passed (1) | 2 passed (2) | |
| origin/antigravity/fix-sync-c0-bootstrap | tenant-isolation.test.ts | FAIL | 1 failed (1) | 1 passed (1) | error: permission denied for table staff_employees |
| origin/codex/fix-format-baseline | health.test.ts | OK | 1 passed (1) | 3 passed (3) | |
| origin/codex/fix-format-baseline | openapi-runtime.test.ts | OK | 1 passed (1) | 1 passed (1) | |
| origin/codex/fix-format-baseline | portal.test.ts | OK | 1 passed (1) | 12 passed (12) | |
| origin/codex/fix-format-baseline | tenant-isolation.test.ts | FAIL | 1 failed (1) | 1 passed (1) | error: permission denied for table staff_employees |
| origin/codex/fix-live-database | health.test.ts | OK | 1 passed (1) | 3 passed (3) | |
| origin/codex/fix-live-database | live-postgres-remediation.test.ts | OK | 1 passed (1) | 1 passed (1) | |
| origin/codex/fix-live-database | openapi-runtime.test.ts | OK | 1 passed (1) | 1 passed (1) | |
| origin/codex/fix-live-database | portal.test.ts | OK | 1 passed (1) | 12 passed (12) | |
| origin/codex/fix-live-database | tenant-isolation.test.ts | FAIL | 1 failed (1) | 1 passed (1) | error: permission denied for table staff_employees |
| origin/codex/fix-pilot-backups | health.test.ts | OK | 1 passed (1) | 3 passed (3) | |
| origin/codex/fix-pilot-backups | openapi-runtime.test.ts | OK | 1 passed (1) | 1 passed (1) | |
| origin/codex/fix-pilot-backups | portal.test.ts | OK | 1 passed (1) | 12 passed (12) | |
| origin/codex/fix-pilot-backups | tenant-isolation.test.ts | FAIL | 1 failed (1) | 1 passed (1) | error: permission denied for table staff_employees |
| origin/codex/fix-tenant-config | health.test.ts | OK | 1 passed (1) | 3 passed (3) | |
| origin/codex/fix-tenant-config | openapi-runtime.test.ts | OK | 1 passed (1) | 1 passed (1) | |
| origin/codex/fix-tenant-config | portal.test.ts | OK | 1 passed (1) | 12 passed (12) | |
| origin/codex/fix-tenant-config | tenant-isolation.test.ts | FAIL | 1 failed (1) | 1 passed (1) | error: permission denied for table staff_employees |
| origin/codex/fix-tenant-rehome | health.test.ts | OK | 1 passed (1) | 3 passed (3) | |
| origin/codex/fix-tenant-rehome | openapi-runtime.test.ts | OK | 1 passed (1) | 1 passed (1) | |
| origin/codex/fix-tenant-rehome | portal.test.ts | OK | 1 passed (1) | 12 passed (12) | |
| origin/codex/fix-tenant-rehome | rehome-tenant.test.ts | OK | 1 passed (1) | 1 passed (1) | |
| origin/codex/fix-tenant-rehome | tenant-isolation.test.ts | FAIL | 1 failed (1) | 1 passed (1) | error: permission denied for table staff_employees |
### Попарные слияния (`git merge-tree --write-tree`)
| Ветка A | Ветка B | Результат | Конфликтные файлы |
| ------------------------------------------- | ------------------------------------------- | --------- | ----------------- |
| origin/codex/bootstrap-platform-foundation | origin/antigravity/fix-reject-device-shifts | чисто | |
| origin/codex/bootstrap-platform-foundation | origin/antigravity/fix-sync-c0-bootstrap | чисто | |
| origin/codex/bootstrap-platform-foundation | origin/codex/fix-format-baseline | чисто | |
| origin/codex/bootstrap-platform-foundation | origin/codex/fix-live-database | чисто | |
| origin/codex/bootstrap-platform-foundation | origin/codex/fix-pilot-backups | чисто | |
| origin/codex/bootstrap-platform-foundation | origin/codex/fix-tenant-config | чисто | |
| origin/codex/bootstrap-platform-foundation | origin/codex/fix-tenant-rehome | чисто | |
| origin/antigravity/fix-reject-device-shifts | origin/antigravity/fix-sync-c0-bootstrap | чисто | |
| origin/antigravity/fix-reject-device-shifts | origin/codex/fix-format-baseline | чисто | |
| origin/antigravity/fix-reject-device-shifts | origin/codex/fix-live-database | чисто | |
| origin/antigravity/fix-reject-device-shifts | origin/codex/fix-pilot-backups | чисто | |
| origin/antigravity/fix-reject-device-shifts | origin/codex/fix-tenant-config | чисто | |
| origin/antigravity/fix-reject-device-shifts | origin/codex/fix-tenant-rehome | чисто | |
| origin/antigravity/fix-sync-c0-bootstrap | origin/codex/fix-format-baseline | чисто | |
| origin/antigravity/fix-sync-c0-bootstrap | origin/codex/fix-live-database | чисто | |
| origin/antigravity/fix-sync-c0-bootstrap | origin/codex/fix-pilot-backups | чисто | |
| origin/antigravity/fix-sync-c0-bootstrap | origin/codex/fix-tenant-config | чисто | |
| origin/antigravity/fix-sync-c0-bootstrap | origin/codex/fix-tenant-rehome | чисто | |
| origin/codex/fix-format-baseline | origin/codex/fix-live-database | чисто | |
| origin/codex/fix-format-baseline | origin/codex/fix-pilot-backups | чисто | |
| origin/codex/fix-format-baseline | origin/codex/fix-tenant-config | чисто | |
| origin/codex/fix-format-baseline | origin/codex/fix-tenant-rehome | чисто | |
| origin/codex/fix-live-database | origin/codex/fix-pilot-backups | чисто | |
| origin/codex/fix-live-database | origin/codex/fix-tenant-config | чисто | |
| origin/codex/fix-live-database | origin/codex/fix-tenant-rehome | чисто | |
| origin/codex/fix-pilot-backups | origin/codex/fix-tenant-config | чисто | |
| origin/codex/fix-pilot-backups | origin/codex/fix-tenant-rehome | чисто | |
| origin/codex/fix-tenant-config | origin/codex/fix-tenant-rehome | чисто | |
### Форматирование без поведения
**origin/codex/fix-format-baseline** (a05777c), merge-base f5ac179, изменено файлов: 19 — ТОЛЬКО ФОРМАТИРОВАНИЕ

View file

@ -0,0 +1,101 @@
# Команда (после git fetch, 16:59 NOVT; база f7d3c73): node scripts/test/accept-branch.mjs $(ветки origin/codex/_, origin/antigravity/_, которые не предки основы)
# = antigravity/fix-reject-device-shifts antigravity/fix-sync-c0-bootstrap codex/fix-format-baseline codex/fix-live-database codex/fix-pilot-backups codex/fix-tenant-config codex/fix-tenant-rehome
# Предки основы (не прогонялись): antigravity/employee-notifications, antigravity/offline-sync-backend, codex/finance-calendar-dashboard, codex/frontend-library, codex/security-finance-production, codex/server-web-production
## accept-branch — 2026-09-26 10:02 UTC
База: `origin/codex/bootstrap-platform-foundation` = f7d3c73; стенд (оверлей tests/e2e, scripts/test): 18f944e; БД: роли созданы один раз через E2E_PG_ADMIN_URL (provision-pg), дальше --roles keep. Логи: `tests/e2e/evidence/accept/2026-09-26T09-59-10/`.
### Сводка
| Ветка | SHA | npm run check | integration (красные файлы) | e2e red/green | e2e: стало зелёным | e2e: стало красным |
| ------------------------------------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- | ------------------------------------------- | ------------------ | ------------------------------- |
| origin/codex/bootstrap-platform-foundation (база) | f7d3c73 | OK (195 passed | 1 skipped (196)) | 3/4 зелёных; FAIL: tenant-isolation.test.ts | 46/5 | n/a | n/a |
| origin/antigravity/fix-reject-device-shifts | b283736 | OK (196 passed | 3 skipped (199)) | 4/5 зелёных; FAIL: tenant-isolation.test.ts | 44/7 | K13/SYNC-A, K13/SYNC-B | — |
| origin/antigravity/fix-sync-c0-bootstrap | 24cc18d | OK (197 passed | 5 skipped (202)) | 5/6 зелёных; FAIL: tenant-isolation.test.ts | 43/8 | K13/SYNC-A, K13/SYNC-B, SYNC-C0 | — |
| origin/codex/fix-format-baseline | a05777c | OK (195 passed | 1 skipped (196)) | 3/4 зелёных; FAIL: tenant-isolation.test.ts | 46/5 | — | — |
| origin/codex/fix-live-database | 1573860 | OK (197 passed | 2 skipped (199)) | 4/5 зелёных; FAIL: tenant-isolation.test.ts | 44/7 | K3/N2, K4/N1 | — |
| origin/codex/fix-pilot-backups | a005c5e | OK (195 passed | 1 skipped (196)) | 3/4 зелёных; FAIL: tenant-isolation.test.ts | 46/5 | — | — |
| origin/codex/fix-tenant-config | 535052e | FAIL exit=1: Error: STANDALONE_TENANT_ID uses the development placeholder, which is allowed only in development on loopback | 3/4 зелёных; FAIL: tenant-isolation.test.ts | 46/5 | — | — |
| origin/codex/fix-tenant-rehome | c73bb3b | OK (197 passed | 2 skipped (199)) | 4/5 зелёных; FAIL: tenant-isolation.test.ts | 46/5 | — | — |
### Интеграционные тесты (каждый файл отдельно, свежая БД)
| Ветка | Файл | Итог | Test Files | Tests | Первая ошибка |
| ------------------------------------------- | --------------------------------- | ---- | ------------ | -------------- | -------------------------------------------------- |
| origin/codex/bootstrap-platform-foundation | health.test.ts | OK | 1 passed (1) | 3 passed (3) | |
| origin/codex/bootstrap-platform-foundation | openapi-runtime.test.ts | OK | 1 passed (1) | 1 passed (1) | |
| origin/codex/bootstrap-platform-foundation | portal.test.ts | OK | 1 passed (1) | 12 passed (12) | |
| origin/codex/bootstrap-platform-foundation | tenant-isolation.test.ts | FAIL | 1 failed (1) | 1 passed (1) | error: permission denied for table staff_employees |
| origin/antigravity/fix-reject-device-shifts | health.test.ts | OK | 1 passed (1) | 3 passed (3) | |
| origin/antigravity/fix-reject-device-shifts | openapi-runtime.test.ts | OK | 1 passed (1) | 1 passed (1) | |
| origin/antigravity/fix-reject-device-shifts | portal.test.ts | OK | 1 passed (1) | 12 passed (12) | |
| origin/antigravity/fix-reject-device-shifts | sync-rejection.test.ts | OK | 1 passed (1) | 2 passed (2) | |
| origin/antigravity/fix-reject-device-shifts | tenant-isolation.test.ts | FAIL | 1 failed (1) | 1 passed (1) | error: permission denied for table staff_employees |
| origin/antigravity/fix-sync-c0-bootstrap | health.test.ts | OK | 1 passed (1) | 3 passed (3) | |
| origin/antigravity/fix-sync-c0-bootstrap | openapi-runtime.test.ts | OK | 1 passed (1) | 1 passed (1) | |
| origin/antigravity/fix-sync-c0-bootstrap | portal.test.ts | OK | 1 passed (1) | 12 passed (12) | |
| origin/antigravity/fix-sync-c0-bootstrap | sync-bootstrap.test.ts | OK | 1 passed (1) | 2 passed (2) | |
| origin/antigravity/fix-sync-c0-bootstrap | sync-rejection.test.ts | OK | 1 passed (1) | 2 passed (2) | |
| origin/antigravity/fix-sync-c0-bootstrap | tenant-isolation.test.ts | FAIL | 1 failed (1) | 1 passed (1) | error: permission denied for table staff_employees |
| origin/codex/fix-format-baseline | health.test.ts | OK | 1 passed (1) | 3 passed (3) | |
| origin/codex/fix-format-baseline | openapi-runtime.test.ts | OK | 1 passed (1) | 1 passed (1) | |
| origin/codex/fix-format-baseline | portal.test.ts | OK | 1 passed (1) | 12 passed (12) | |
| origin/codex/fix-format-baseline | tenant-isolation.test.ts | FAIL | 1 failed (1) | 1 passed (1) | error: permission denied for table staff_employees |
| origin/codex/fix-live-database | health.test.ts | OK | 1 passed (1) | 3 passed (3) | |
| origin/codex/fix-live-database | live-postgres-remediation.test.ts | OK | 1 passed (1) | 1 passed (1) | |
| origin/codex/fix-live-database | openapi-runtime.test.ts | OK | 1 passed (1) | 1 passed (1) | |
| origin/codex/fix-live-database | portal.test.ts | OK | 1 passed (1) | 12 passed (12) | |
| origin/codex/fix-live-database | tenant-isolation.test.ts | FAIL | 1 failed (1) | 1 passed (1) | error: permission denied for table staff_employees |
| origin/codex/fix-pilot-backups | health.test.ts | OK | 1 passed (1) | 3 passed (3) | |
| origin/codex/fix-pilot-backups | openapi-runtime.test.ts | OK | 1 passed (1) | 1 passed (1) | |
| origin/codex/fix-pilot-backups | portal.test.ts | OK | 1 passed (1) | 12 passed (12) | |
| origin/codex/fix-pilot-backups | tenant-isolation.test.ts | FAIL | 1 failed (1) | 1 passed (1) | error: permission denied for table staff_employees |
| origin/codex/fix-tenant-config | health.test.ts | OK | 1 passed (1) | 3 passed (3) | |
| origin/codex/fix-tenant-config | openapi-runtime.test.ts | OK | 1 passed (1) | 1 passed (1) | |
| origin/codex/fix-tenant-config | portal.test.ts | OK | 1 passed (1) | 12 passed (12) | |
| origin/codex/fix-tenant-config | tenant-isolation.test.ts | FAIL | 1 failed (1) | 1 passed (1) | error: permission denied for table staff_employees |
| origin/codex/fix-tenant-rehome | health.test.ts | OK | 1 passed (1) | 3 passed (3) | |
| origin/codex/fix-tenant-rehome | openapi-runtime.test.ts | OK | 1 passed (1) | 1 passed (1) | |
| origin/codex/fix-tenant-rehome | portal.test.ts | OK | 1 passed (1) | 12 passed (12) | |
| origin/codex/fix-tenant-rehome | rehome-tenant.test.ts | OK | 1 passed (1) | 1 passed (1) | |
| origin/codex/fix-tenant-rehome | tenant-isolation.test.ts | FAIL | 1 failed (1) | 1 passed (1) | error: permission denied for table staff_employees |
### Попарные слияния (`git merge-tree --write-tree`)
| Ветка A | Ветка B | Результат | Конфликтные файлы |
| ------------------------------------------- | ------------------------------------------- | --------- | ----------------- |
| origin/codex/bootstrap-platform-foundation | origin/antigravity/fix-reject-device-shifts | чисто | |
| origin/codex/bootstrap-platform-foundation | origin/antigravity/fix-sync-c0-bootstrap | чисто | |
| origin/codex/bootstrap-platform-foundation | origin/codex/fix-format-baseline | чисто | |
| origin/codex/bootstrap-platform-foundation | origin/codex/fix-live-database | чисто | |
| origin/codex/bootstrap-platform-foundation | origin/codex/fix-pilot-backups | чисто | |
| origin/codex/bootstrap-platform-foundation | origin/codex/fix-tenant-config | чисто | |
| origin/codex/bootstrap-platform-foundation | origin/codex/fix-tenant-rehome | чисто | |
| origin/antigravity/fix-reject-device-shifts | origin/antigravity/fix-sync-c0-bootstrap | чисто | |
| origin/antigravity/fix-reject-device-shifts | origin/codex/fix-format-baseline | чисто | |
| origin/antigravity/fix-reject-device-shifts | origin/codex/fix-live-database | чисто | |
| origin/antigravity/fix-reject-device-shifts | origin/codex/fix-pilot-backups | чисто | |
| origin/antigravity/fix-reject-device-shifts | origin/codex/fix-tenant-config | чисто | |
| origin/antigravity/fix-reject-device-shifts | origin/codex/fix-tenant-rehome | чисто | |
| origin/antigravity/fix-sync-c0-bootstrap | origin/codex/fix-format-baseline | чисто | |
| origin/antigravity/fix-sync-c0-bootstrap | origin/codex/fix-live-database | чисто | |
| origin/antigravity/fix-sync-c0-bootstrap | origin/codex/fix-pilot-backups | чисто | |
| origin/antigravity/fix-sync-c0-bootstrap | origin/codex/fix-tenant-config | чисто | |
| origin/antigravity/fix-sync-c0-bootstrap | origin/codex/fix-tenant-rehome | чисто | |
| origin/codex/fix-format-baseline | origin/codex/fix-live-database | чисто | |
| origin/codex/fix-format-baseline | origin/codex/fix-pilot-backups | чисто | |
| origin/codex/fix-format-baseline | origin/codex/fix-tenant-config | чисто | |
| origin/codex/fix-format-baseline | origin/codex/fix-tenant-rehome | чисто | |
| origin/codex/fix-live-database | origin/codex/fix-pilot-backups | чисто | |
| origin/codex/fix-live-database | origin/codex/fix-tenant-config | чисто | |
| origin/codex/fix-live-database | origin/codex/fix-tenant-rehome | чисто | |
| origin/codex/fix-pilot-backups | origin/codex/fix-tenant-config | чисто | |
| origin/codex/fix-pilot-backups | origin/codex/fix-tenant-rehome | чисто | |
| origin/codex/fix-tenant-config | origin/codex/fix-tenant-rehome | чисто | |
### Форматирование без поведения
**origin/codex/fix-format-baseline** (a05777c), merge-base f5ac179, изменено файлов: 19 — ТОЛЬКО ФОРМАТИРОВАНИЕ

View file

@ -0,0 +1,379 @@
# K18 (codex/fix-pilot-backups a005c5e) и перенос арендатора (codex/fix-tenant-rehome c73bb3b) на восстановленной копии
# PG17 17.11 локально, без Docker. Ветки исполнителей не менялись: отдельные detached worktree
# /workspace/krona-staff-acceptance/diag-pilot-backups и diag-tenant-rehome.
## 1. Тесты Codex из ветки (моки docker/ssh/scp)
$ bash scripts/test-backup.sh; bash scripts/test-backup-pull.sh
backup tests passed: syntax, CMS round-trip, pg_dump/OpenSSL/scp failures, no plaintext dump on disk (exit=0)
backup pull tests passed: CMS+checksum, idempotence, stale/mismatch rejection, path validation, scp failure (exit=0)
## 2. Учебное восстановление: настоящий pg_dump → backup.sh → sha256 → расшифровка → pg_restore → отчёты
# docker подменён шимом: 'docker compose exec -T postgres sh -eu -c <cmd>' выполняет <cmd> локально
# (pg_dump -U pgadmin -d krona_k18_src -Fc); источник — копия БД e2e (заглушка, посторонние арендаторы, данные e2e).
$ bash drill.sh
== 1. источник: копия krona_acc_e2e (данные последнего e2e-прогона accept-branch)
== 2. deploy/pilot/backup.sh (external-pull)
backup=<tmp>/server/backups/krona-staff-20260926T101055Z-1837466.dump.cms
checksum=<tmp>/server/backups/krona-staff-20260926T101055Z-1837466.dump.cms.sha256
off_host_copy=pending_pull
exit=0
plaintext на сервере: 0 файлов
первые байты (DER): 30 80 06 09
== 3. «перенос на ПК» (cp вместо ssh/scp — pull-pilot-backups.sh здесь не запускался) + sha256
krona-staff-20260926T101055Z-1837466.dump.cms: OK
== 4. расшифровка ключом вне сервера + pg_restore в новую БД
TABLE DATA в дампе: 28
pg_restore exit=0
== 5. restore-drill-report.sql: источник vs копия
строк отчёта: 79; diff:
идентичны
RLS|27|t
LEDGER|<ulid>|RUB|263568
LEDGER|<ulid>|RUB|22200
LEDGER|<ulid>|RUB|329705
schema_migrations: 11
staff_payroll_ledger owner=krona_migrator
гранты krona_staff_app: 100
== 6. restore-drill-isolation.sql (krona_staff_app, NOBYPASSRLS)
BEGIN
SET
SET
source-employee-count|3
SET
target-employee-count|0
ROLLBACK
## 3. Перенос заглушки на копии, интерфейс продукта как есть
$ bash rehome-on-copy.sh # DATABASE_REHOME_URL=<суперпользователь>@krona_k18_restore
== dry-run: node scripts/rehome-tenant.mjs --dry-run --source <STUB> --target <TARGET>
exit=0
{"mode":"dry-run","source":"<STUB>","target":"<TARGET>","tables":{"staff_attendance_events":"2","staff_audit_log":"2","staff_daily_metrics":"2","staff_employees":"3","staff_idempotency_keys":"2","staff_inbox":"2","staff_metric_definitions":"2","staff_notification_deliveries":"2","staff_org_unit_terms":"2","staff_org_units":"2","staff_outbox":"2","staff_pay_assignments":"2","staff_pay_rules":"2","staff_payouts":"2","staff_payroll_ledger":"3","staff_payroll_lines":"2","staff_payroll_runs":"2","staff_professions":"2","staff_push_subscriptions":"2","staff_standalone_employee_access":"2","staff_sync_change_log":"2","staff_sync_command_inbox":"2","staff_sync_device_checkpoints":"2","staff_sync_devices":"2","staff_timesheet_entries":"2","staff_work_items":"2","staff_work_sessions":"2"}}
dry-run: БД не изменилась
== C6-интерфейс на этом же скрипте
Tenant rehome stopped. Unknown option --from. No connection details or row contents were printed.
exit=1
== apply без --backup-evidence
Tenant rehome stopped. --backup-evidence is required for --apply. No connection details or row contents were printed.
exit=1
== apply: --apply --source <STUB> --target <TARGET> --actor <ulid> --backup-evidence evidence.json
exit=0
{"mode":"apply","source":"<STUB>","target":"<TARGET>","tables":{"staff_attendance_events":"2","staff_audit_log":"3","staff_daily_metrics":"2","staff_employees":"3","staff_idempotency_keys":"2","staff_inbox":"2","staff_metric_definitions":"2","staff_notification_deliveries":"2","staff_org_unit_terms":"2","staff_org_units":"2","staff_outbox":"2","staff_pay_assignments":"2","staff_pay_rules":"2","staff_payouts":"2","staff_payroll_ledger":"3","staff_payroll_lines":"2","staff_payroll_runs":"2","staff_professions":"2","staff_push_subscriptions":"2","staff_standalone_employee_access":"2","staff_sync_change_log":"2","staff_sync_command_inbox":"2","staff_sync_device_checkpoints":"2","staff_sync_devices":"2","staff_timesheet_entries":"2","staff_work_items":"2","staff_work_sessions":"2"},"auditRowsAdded":1,"payrollLedger":[{"currency":"RUB","amountMinor":"263568"}]}
== сверка (restore-drill-report.sql до/после; счётчики по таблице/арендатору, RLS, ledger)
-- строки с <STUB> после: 0
-- прочие арендаторы до/после идентичны: да
-- счётчики <STUB> до vs <TARGET> после (кроме audit):
совпадают
-- audit: до 2, после 3
LEDGER|<TARGET>|RUB|263568
LEDGER|<other>|RUB|22200
LEDGER|<other>|RUB|329705
до: LEDGER|<STUB>|RUB|263568
до: LEDGER|<other>|RUB|22200
до: LEDGER|<other>|RUB|329705
tenant.rehomed tenant_rehome
FK после: 30
== krona_staff_app
source-employee-count|0
target-employee-count|3
ERROR: permission denied for table staff_payroll_ledger
== повторный apply
Tenant rehome stopped. Target tenant already contains staff data; refusing merge. No connection details or row contents were printed.
exit=1
повтор: БД не изменилась
FK (определения + convalidated), md5 по всем FK: источник 54ee68489eced699e40a343f4ba4134e, копия после переноса 54ee68489eced699e40a343f4ba4134e
## 4. Сценарий [PILOT-REHOME] через диагностический адаптер C6 → c73bb3b (адаптер НЕ в продукте и НЕ в сценарии)
# Вариант 1: адаптер только переводит флаги/переменные/формат вывода.
$ E2E_DATABASE_NAME=krona_diag_rehome_e2e node scripts/test/run-e2e.mjs
✓ tests/e2e/60-pilot-rehome.e2e.ts > PILOT-REHOME: перенос заглушки арендатора на настоящий tenant > [PILOT-REHOME-SEED] сиды покрывают все таблицы с tenant_id (список из information_schema) 183ms
× tests/e2e/60-pilot-rehome.e2e.ts > PILOT-REHOME: перенос заглушки арендатора на настоящий tenant > [PILOT-REHOME] --dry-run, перенос админом, сверка, повторный запуск отказывает 96ms
→ expected 'undefined' to be '328228' // Object.is equality
# Вариант 2: адаптер дополнительно сам досчитывает SUM(amount_minor) для --dry-run (продукт её там не печатает).
✓ tests/e2e/60-pilot-rehome.e2e.ts > PILOT-REHOME: перенос заглушки арендатора на настоящий tenant > [PILOT-REHOME-SEED] сиды покрывают все таблицы с tenant_id (список из information_schema) 177ms
✓ tests/e2e/60-pilot-rehome.e2e.ts > PILOT-REHOME: перенос заглушки арендатора на настоящий tenant > [PILOT-REHOME] --dry-run, перенос админом, сверка, повторный запуск отказывает 460ms
## 5. Восстановление в «голый» кластер (ролей нет)
Тот же `pg_dump -Fc` (источник krona_k18_src, владелец таблиц krona_migrator, как в стенде e2e),
восстановление во временный кластер PG17 (initdb, порт 55439), где ролей krona_* нет:
```text
$ createdb krona_staff (свежий кластер PG17, ролей krona_* нет) && pg_restore --exit-on-error -d krona_staff d.dump
pg_restore: error: could not execute query: ERROR: role "krona_migrator" does not exist
Command was: ALTER SCHEMA public OWNER TO krona_migrator;
exit=1
$ pg_restore без --exit-on-error (как по умолчанию)
exit=1
ошибок: 61
pg_restore: error: could not execute query: ERROR: role "krona_migrator" does not exist
154 # staff_employees: строки восстановлены
postgres # владелец staff_employees
0 # ролей krona_* в кластере
```
В пилоте владелец — POSTGRES_USER, а krona_staff_app создаёт `docker/postgres/init-app-role.sh`
при инициализации контейнера — там роли, вероятно, будут; без Docker не проверено.
### Исходный код адаптера (вариант 2)
```js
// ДИАГНОСТИЧЕСКИЙ АДАПТЕР (не коммитится в продукт, не часть сценария): переводит контракт C6
// (--from/--to/--dry-run/--json, DATABASE_ADMIN_URL, одна строка JSON с ledger_amount_minor_sum)
// в интерфейс codex/fix-tenant-rehome c73bb3b (--source/--target, --dry-run|--apply, --actor,
// --backup-evidence, DATABASE_REHOME_URL, многострочный JSON). Evidence — синтетическая.
import { spawnSync } from 'node:child_process';
import { writeFileSync, mkdtempSync } from 'node:fs';
import { tmpdir } from 'node:os';
import path from 'node:path';
const a = process.argv.slice(2);
const get = (k) => a[a.indexOf(k) + 1];
const dry = a.includes('--dry-run');
const args = ['--source', get('--from'), '--target', get('--to')];
if (dry) args.unshift('--dry-run');
else {
const dir = mkdtempSync(path.join(tmpdir(), 'rehome-ev-'));
const sha = 'b'.repeat(64);
writeFileSync(
path.join(dir, 'e.json'),
JSON.stringify({
backup: {
format: 'pg_dump -Fc',
encrypted: true,
offHost: true,
sha256: sha,
createdAt: '2026-09-26T10:00:00Z',
},
restoreDrill: {
status: 'passed',
backupSha256: sha,
restoredAt: '2026-09-26T10:01:00Z',
verifiedAt: '2026-09-26T10:02:00Z',
isolatedDatabase: 'diag',
},
}),
);
args.unshift('--apply');
args.push('--actor', '01M3ACT0R000000000000000AA', '--backup-evidence', path.join(dir, 'e.json'));
}
const r = spawnSync(
process.execPath,
[path.join(import.meta.dirname, 'rehome-tenant.product.mjs'), ...args],
{
env: { ...process.env, DATABASE_REHOME_URL: process.env.DATABASE_ADMIN_URL },
encoding: 'utf8',
},
);
process.stderr.write(r.stderr);
if (r.status !== 0) process.exit(r.status ?? 1);
const rep = JSON.parse(r.stdout);
const out = {
tables: Object.fromEntries(Object.entries(rep.tables).map(([k, v]) => [k, Number(v)])),
};
if (rep.payrollLedger)
out.ledger_amount_minor_sum = rep.payrollLedger
.reduce((s, x) => s + BigInt(x.amountMinor), 0n)
.toString();
if (dry && !out.ledger_amount_minor_sum) {
// ВАРИАНТ 2: сумму досчитывает сам адаптер (продукт её в dry-run не печатает), чтобы проверить остальные шаги.
const pg = (await import('pg')).default;
const c = new pg.Client({ connectionString: process.env.DATABASE_ADMIN_URL });
await c.connect();
out.ledger_amount_minor_sum = (
await c.query(
'SELECT coalesce(sum(amount_minor),0)::text s FROM staff_payroll_ledger WHERE tenant_id=$1',
[get('--from')],
)
).rows[0].s;
await c.end();
}
console.log(JSON.stringify(out));
```
### Исходный код drill.sh
```bash
#!/usr/bin/env bash
# K18: backup.sh (codex/fix-pilot-backups a005c5e) → шифрованный файл → проверка sha256 → расшифровка
# → pg_restore в новую БД → restore-drill-report.sql на источнике и копии → сравнение → isolation.
# Docker нет: `docker compose exec -T postgres sh -eu -c '<cmd>'` подменён шимом, который выполняет
# ту же команду pg_dump локально против PG17 (суперпользователь, как POSTGRES_USER в образе postgres).
set -euo pipefail
WT=/workspace/krona-staff-acceptance/diag-pilot-backups
PGBIN=/workspace/.mamba-root/envs/pg17/bin
export PGHOST=127.0.0.1 PGPORT=55433 PGUSER=pgadmin PGPASSWORD="$(cat /workspace/.pg17-secrets/admin.pw)"
psql() { "$PGBIN/psql" -X -v ON_ERROR_STOP=1 "$@"; }
T=$(mktemp -d /tmp/k18-drill.XXXXXX); trap 'rm -rf "$T"' EXIT
mkdir -p "$T/bin" "$T/server" "$T/pc" "$T/offhost-key"
echo "== 1. источник: копия krona_acc_e2e (данные последнего e2e-прогона accept-branch)"
psql -d postgres -q -c "DROP DATABASE IF EXISTS krona_k18_src" -c "DROP DATABASE IF EXISTS krona_k18_restore" \
-c "CREATE DATABASE krona_k18_src TEMPLATE krona_acc_e2e OWNER krona_migrator" \
-c "REVOKE ALL ON DATABASE krona_k18_src FROM PUBLIC" -c "GRANT CONNECT ON DATABASE krona_k18_src TO krona_staff_app"
cat > "$T/bin/docker" <<SHIM
#!/bin/sh
for last; do :; done
exec env PATH="$PGBIN:\$PATH" POSTGRES_USER=pgadmin POSTGRES_DB=krona_k18_src sh -eu -c "\$last"
SHIM
chmod +x "$T/bin/docker"
openssl req -x509 -newkey rsa:2048 -nodes -sha256 -days 1 -keyout "$T/offhost-key/recipient.key" \
-out "$T/server/recipient.crt" -subj '/CN=k18-drill' >/dev/null 2>&1
echo "== 2. deploy/pilot/backup.sh (external-pull)"
PATH="$T/bin:$PATH" BACKUP_DIRECTORY="$T/server/backups" BACKUP_RECIPIENT_CERT="$T/server/recipient.crt" \
BACKUP_TRANSFER_MODE=external-pull sh "$WT/deploy/pilot/backup.sh" | sed "s#$T#<tmp>#g"
echo "exit=$?"
f=$(ls "$T/server/backups"/*.dump.cms); n=$(basename "$f")
echo "plaintext на сервере: $(find "$T/server/backups" -type f ! -name '*.dump.cms' ! -name '*.dump.cms.sha256' | wc -l) файлов"
head -c 4 "$f" | od -An -tx1 | sed 's/^/первые байты (DER): /'
echo "== 3. «перенос на ПК» (cp вместо ssh/scp — pull-pilot-backups.sh здесь не запускался) + sha256"
cp "$f" "$f.sha256" "$T/pc/" && (cd "$T/pc" && sha256sum -c "$n.sha256")
sha=$(cut -d' ' -f1 "$T/pc/$n.sha256")
echo "== 4. расшифровка ключом вне сервера + pg_restore в новую БД"
openssl cms -decrypt -binary -inform DER -in "$T/pc/$n" -recip "$T/server/recipient.crt" \
-inkey "$T/offhost-key/recipient.key" -out "$T/pc/restore.dump"
"$PGBIN/pg_restore" -l "$T/pc/restore.dump" | grep -c "TABLE DATA" | sed 's/^/TABLE DATA в дампе: /'
psql -d postgres -q -c "CREATE DATABASE krona_k18_restore TEMPLATE template0 OWNER krona_migrator" \
-c "REVOKE ALL ON DATABASE krona_k18_restore FROM PUBLIC" -c "GRANT CONNECT ON DATABASE krona_k18_restore TO krona_staff_app"
"$PGBIN/pg_restore" --exit-on-error -d krona_k18_restore "$T/pc/restore.dump"; echo "pg_restore exit=$?"
echo "== 5. restore-drill-report.sql: источник vs копия"
psql -d krona_k18_src -At -f "$WT/scripts/restore-drill-report.sql" > "$T/src.txt"
psql -d krona_k18_restore -At -f "$WT/scripts/restore-drill-report.sql" > "$T/restore.txt"
echo "строк отчёта: $(wc -l < "$T/src.txt"); diff:"; diff "$T/src.txt" "$T/restore.txt" && echo "идентичны"
grep -E '^(RLS|LEDGER)' "$T/restore.txt" | sed -E 's/[0-9A-HJKMNP-TV-Z]{26}/<ulid>/g'
psql -d krona_k18_restore -At -c "select count(*) from schema_migrations" | sed 's/^/schema_migrations: /'
psql -d krona_k18_restore -At -c "select tablename||' owner='||tableowner from pg_tables where schemaname='public' and tablename='staff_payroll_ledger'"
psql -d krona_k18_restore -At -c "select count(*) from information_schema.role_table_grants where grantee='krona_staff_app'" | sed 's/^/гранты krona_staff_app: /'
echo "== 6. restore-drill-isolation.sql (krona_staff_app, NOBYPASSRLS)"
psql -d krona_k18_restore -At -f "$WT/scripts/restore-drill-isolation.sql"
printf '%s\n' "$sha" > /tmp/ab/k18/backup.sha256
```
### Исходный код rehome-on-copy.sh
```bash
#!/usr/bin/env bash
# TASK-CODEX-2 п.2.2 на восстановленной копии (krona_k18_restore из drill.sh), интерфейс продукта c73bb3b как есть.
set -uo pipefail
WT=/workspace/krona-staff-acceptance/diag-tenant-rehome
BK=/workspace/krona-staff-acceptance/diag-pilot-backups
PGBIN=/workspace/.mamba-root/envs/pg17/bin
export PGHOST=127.0.0.1 PGPORT=55433 PGUSER=pgadmin PGPASSWORD="$(cat /workspace/.pg17-secrets/admin.pw)"
DB=krona_k18_restore
URL="postgres://pgadmin:${PGPASSWORD}@127.0.0.1:55433/$DB"
STUB=01J00000000000000000000000; TARGET=01M2R6AC5652NPT3MG34DKN4ZP
psql() { "$PGBIN/psql" -X -v ON_ERROR_STOP=1 "$@"; }
mask() { sed -E "s/$STUB/<STUB>/g; s/$TARGET/<TARGET>/g; s/[0-9A-HJKMNP-TV-Z]{26}/<other>/g"; }
cd "$WT"
psql -d $DB -At -f "$BK/scripts/restore-drill-report.sql" > /tmp/ab/k18/before.txt
echo "== dry-run: node scripts/rehome-tenant.mjs --dry-run --source <STUB> --target <TARGET>"
DATABASE_REHOME_URL="$URL" node scripts/rehome-tenant.mjs --dry-run --source $STUB --target $TARGET > /tmp/ab/k18/dry.json; echo "exit=$?"
mask < /tmp/ab/k18/dry.json | tr -d '\n ' | cut -c1-900; echo
psql -d $DB -At -f "$BK/scripts/restore-drill-report.sql" > /tmp/ab/k18/after-dry.txt
diff -q /tmp/ab/k18/before.txt /tmp/ab/k18/after-dry.txt && echo "dry-run: БД не изменилась"
echo "== C6-интерфейс на этом же скрипте"
DATABASE_ADMIN_URL="$URL" node scripts/rehome-tenant.mjs --from $STUB --to $TARGET --dry-run --json; echo "exit=$?"
echo "== apply без --backup-evidence"
DATABASE_REHOME_URL="$URL" node scripts/rehome-tenant.mjs --apply --source $STUB --target $TARGET --actor 01M3ACT0R000000000000000AA; echo "exit=$?"
sha=$(cat /tmp/ab/k18/backup.sha256)
cat > /tmp/ab/k18/evidence.json <<JSON
{"backup":{"format":"pg_dump -Fc","encrypted":true,"offHost":true,"sha256":"$sha","createdAt":"2026-09-26T10:10:55Z"},
"restoreDrill":{"status":"passed","backupSha256":"$sha","restoredAt":"2026-09-26T10:11:00Z","verifiedAt":"2026-09-26T10:11:05Z","isolatedDatabase":"$DB"}}
JSON
echo "== apply: --apply --source <STUB> --target <TARGET> --actor <ulid> --backup-evidence evidence.json"
DATABASE_REHOME_URL="$URL" node scripts/rehome-tenant.mjs --apply --source $STUB --target $TARGET --actor 01M3ACT0R000000000000000AA --backup-evidence /tmp/ab/k18/evidence.json > /tmp/ab/k18/apply.json; echo "exit=$?"
mask < /tmp/ab/k18/apply.json | tr -d '\n ' | cut -c1-1200; echo
psql -d $DB -At -f "$BK/scripts/restore-drill-report.sql" > /tmp/ab/k18/after.txt
echo "== сверка (restore-drill-report.sql до/после; счётчики по таблице/арендатору, RLS, ledger)"
echo "-- строки с <STUB> после: $(grep -c "|$STUB|" /tmp/ab/k18/after.txt)"
echo "-- прочие арендаторы до/после идентичны: $(diff <(grep -v -e "|$STUB|" -e "|$TARGET|" /tmp/ab/k18/before.txt) <(grep -v -e "|$STUB|" -e "|$TARGET|" /tmp/ab/k18/after.txt) >/dev/null && echo да || echo НЕТ)"
echo "-- счётчики <STUB> до vs <TARGET> после (кроме audit):"
diff <(grep "|$STUB|" /tmp/ab/k18/before.txt | grep -v '^staff_audit_log' | sed "s/$STUB/X/") <(grep "|$TARGET|" /tmp/ab/k18/after.txt | grep -v '^staff_audit_log' | sed "s/$TARGET/X/") && echo " совпадают"
echo "-- audit: до $(grep "^staff_audit_log|$STUB|" /tmp/ab/k18/before.txt | cut -d'|' -f3), после $(grep "^staff_audit_log|$TARGET|" /tmp/ab/k18/after.txt | cut -d'|' -f3)"
grep "^LEDGER" /tmp/ab/k18/after.txt | mask; grep "^LEDGER" /tmp/ab/k18/before.txt | mask | sed 's/^/до: /'
psql -d $DB -At -c "select action||' '||entity_type from staff_audit_log where tenant_id='$TARGET' and action like 'tenant.%'"
psql -d $DB -At -c "select count(*) from pg_constraint where contype='f' and connamespace='public'::regnamespace" | sed 's/^/FK после: /'
echo "== krona_staff_app"
psql -d $DB -At -f "$BK/scripts/restore-drill-isolation.sql" | grep count
psql -d $DB -At -c "BEGIN; SET LOCAL ROLE krona_staff_app; SET LOCAL app.tenant_id='$TARGET'; UPDATE staff_payroll_ledger SET amount_minor=amount_minor+1;" 2>&1 | tail -1
echo "== повторный apply"
DATABASE_REHOME_URL="$URL" node scripts/rehome-tenant.mjs --apply --source $STUB --target $TARGET --actor 01M3ACT0R000000000000000AA --backup-evidence /tmp/ab/k18/evidence.json; echo "exit=$?"
psql -d $DB -At -f "$BK/scripts/restore-drill-report.sql" | diff -q - /tmp/ab/k18/after.txt && echo "повтор: БД не изменилась"
```
## 6. Ветки сдвинулись во время приёмки (17:02 и 17:07 NOVT)
После `git fetch` (~17:13 NOVT): `codex/fix-tenant-rehome` c73bb3b → **d52ecf3** (`fix: accept audit harness tenant
rehome CLI aliases`, 17:02:16 +0700), `codex/fix-pilot-backups` a005c5e → **b022885** (`ops: schedule home pilot
backup and retain verified PC copies`, 17:07:36 +0700). Приёмка повторена по всем 7 веткам:
[`round3-accept-f7d3c73-rerun.md`](round3-accept-f7d3c73-rerun.md) (стенд a6a1b5e, логи
`tests/e2e/evidence/accept/2026-09-26T10-14-42/`).
### 6.1 d52ecf3: что изменилось в интерфейсе
`git diff c73bb3b origin/codex/fix-tenant-rehome -- scripts/rehome-tenant.mjs`: добавлены `--from`/`--to` (синонимы
`--source`/`--target`), `--json` (принимается, вывод и так JSON), `DATABASE_ADMIN_URL` (первым, дальше
`DATABASE_REHOME_URL`, `DATABASE_MIGRATION_URL`); `result.mode ??= 'dry-run'` — **без флага теперь dry-run**, а не
ошибка; `--apply` по-прежнему требует `--actor` и `--backup-evidence`. Вывод остался многострочным
(`JSON.stringify(…, null, 2)`), счётчики — строки, в dry-run суммы журнала нет.
### 6.2 [PILOT-REHOME] на d52ecf3, как есть (accept-branch)
```
× … [PILOT-REHOME] --dry-run, перенос админом, сверка, повторный запуск отказывает 73ms
AssertionError: dry-run без JSON-отчёта: {
"mode": "dry-run",
"source": "01J00000000000000000000000",
"target": "01M2R6AC5652NPT3MG34DKN4ZP",
"tables": {
"staff_attendance_events": "2",
…
}
}
: expected null not to be null
❯ tests/e2e/60-pilot-rehome.e2e.ts:468:70
```
Интерфейс C6: «`--json`: последней строкой stdout — `{…}`»; продукт печатает JSON на нескольких строках.
### 6.3 Диагностика: что упадёт следующим (правки только в неотслеживаемой копии сценария в worktree приёмки, потом откатил)
`node scripts/test/run-e2e.mjs` в `wt-codex-fix-tenant-rehome` (d52ecf3), каждый шаг добавляет одну правку к предыдущему:
| Правка в копии сценария | Итог `[PILOT-REHOME]` |
| --------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| парсер принимает весь stdout как JSON | `expected { staff_attendance_events: '2', …(26) } to deeply equal { staff_attendance_events: 2, …(26) }` |
| + счётчики через `Number()` (это **ошибка сценария**, исправлена в d477dde) | `expected 'undefined' to be '375888'` — в dry-run нет `ledger_amount_minor_sum` |
| + убрана проверка суммы в dry-run | `под заглушкой остались строки: expected [ 'staff_attendance_events', …(26) ] to deeply equal []` — вызов без флага (`--json`) теперь dry-run, exit 0, переноса нет |
После отката: `diff -q tests/e2e/60-pilot-rehome.e2e.ts /workspace/krona-staff/tests/e2e/60-pilot-rehome.e2e.ts && echo restored` → `restored`.
### 6.4 b022885 (K18)
```
$ git worktree add --detach …/diag-pb2 origin/codex/fix-pilot-backups # b022885
$ bash scripts/test-backup.sh | tail -1
backup tests passed: syntax, CMS round-trip, pg_dump/OpenSSL/scp failures, no plaintext dump on disk
test-backup rc=0
$ bash scripts/test-backup-pull.sh | tail -1
backup pull tests passed: CMS+checksum, idempotence, stale/mismatch rejection, path validation, scp failure
test-backup-pull rc=0
```
Новое: ротация копий на ПК в `pull-pilot-backups.sh` (`BACKUP_LOCAL_RETENTION_DAYS`, по умолчанию 90; удаляет
только пары с корректным sha256, симлинки и посторонние файлы оставляет; в `test-backup-pull.sh` есть сценарий
`local-retention`), `deploy/pilot/krona-staff-backup-user.service` (пользовательский systemd-сервис),
`scripts/run-pilot-backup-pull.ps1` (задача Windows), `deploy/pilot/backup.home-server.example`. Ротации на сервере
нет (в `docs/BACKUP-RESTORE.md`: «server-side retention policy» — условие включения расписания). В
`run-pilot-backup-pull.ps1` и `backup.home-server.example` зашиты личные пути (`C:\Users\trush\…`,
`/home/ochenstarik/…`). Systemd-юнит, задача Windows и ротация на реальном диске — не проверено.