Compare commits
8 commits
codex/boot
...
grok/e2e-h
| Author | SHA1 | Date | |
|---|---|---|---|
| bf80b5040e | |||
| 75f76430d4 | |||
| d477dde387 | |||
| a6a1b5e937 | |||
| 404365c644 | |||
| e33e593c99 | |||
| 43b684e04f | |||
| 18f944e3c3 |
7 changed files with 740 additions and 4 deletions
|
|
@ -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`),
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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`.
|
||||||
|
|
|
||||||
|
|
@ -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 сотрудник) не перепроверялось — данных пилота у меня нет.
|
||||||
|
|
|
||||||
95
tests/e2e/report-evidence/round3-accept-f7d3c73-rerun.md
Normal file
95
tests/e2e/report-evidence/round3-accept-f7d3c73-rerun.md
Normal 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 — ТОЛЬКО ФОРМАТИРОВАНИЕ
|
||||||
101
tests/e2e/report-evidence/round3-accept-f7d3c73.md
Normal file
101
tests/e2e/report-evidence/round3-accept-f7d3c73.md
Normal 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 — ТОЛЬКО ФОРМАТИРОВАНИЕ
|
||||||
379
tests/e2e/report-evidence/round3-k18-and-rehome.md
Normal file
379
tests/e2e/report-evidence/round3-k18-and-rehome.md
Normal 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 и ротация на реальном диске — не проверено.
|
||||||
Loading…
Reference in a new issue