krona-staff/tests/e2e/REPORT.md
grok 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

444 lines
56 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# PR: e2e-стенд на реальной PostgreSQL 17 и независимая приёмка (grok/e2e-harness)
База: `codex/bootstrap-platform-foundation` @ `f5ac179` (содержит `331e490`). Ветка перебазирована
без конфликтов. Сравнение:
<https://git.ochenstarik.ru/ochenstarik/krona-staff/compare/codex/bootstrap-platform-foundation...grok/e2e-harness>
Изменены только `tests/e2e/**`, `tests/integration/tenant-isolation.test.ts`,
`.forgejo/workflows/ci.yml`, `scripts/test/**` и одна строка в корневом `.gitignore`
(`tests/e2e/evidence/`, по требованию TASK-GROK-2 §1). `src/`, `apps/`, `migrations/`,
`deploy/`, `package.json` не тронуты. Всё, что требует правки продукта, — в
[`NOTES-FOR-EXECUTORS.md`](NOTES-FOR-EXECUTORS.md).
## Зачем
156 зелёных тестов на поддельной БД не заметили, что создание сотрудника, подтверждение
смены и расчёт зарплаты на реальной PostgreSQL падают всегда. Стенд запускает реальный сервер
под ролью приложения без привилегий на реальной PostgreSQL 17, миграции применяет мигратор без
SUPERUSER, все токены и пароли случайные (`crypto.randomBytes`). Каждый сценарий проверяет
**правильное** поведение: красный = открытый дефект.
## Что изменилось во втором круге (TASK-GROK-2)
| Пункт | Что сделано |
| ----- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| §1 | Запуск без `.cmd`-обёрток: `scripts/test/node-commands.mjs` — всегда `process.execPath` + JS-точка входа из `require.resolve` (`vitest.mjs`, `prettier.cjs`); сервер — один процесс `node --import tsx src/server.ts` (без дочернего процесса CLI tsx, чтобы `kill()` под Windows не оставлял сироту). Проверка на Linux — `tests/e2e/harness/node-commands.test.ts` (в `npm test`) |
| §1 | `tests/e2e/evidence/` убран из git и добавлен в `.gitignore`; в CI — артефакт `e2e-evidence`. Разовые доказательства (N5, N13, приёмка) — в `tests/e2e/report-evidence/`, скрипты туда не пишут |
| §1 | Задача `windows` в CI (включается переменной `KRONA_WINDOWS_RUNNER=true`, раннера пока нет) |
| §1 | Перебазирование на `f5ac179` — без конфликтов |
| §2 | Двойные ID `[владелец/мой]` в названиях тестов и ключах `known-defects.json` |
| §3 | Новые сценарии `[K6/D5]`, `[K7/D2]`, `[K8/D1]`, `[K9/D3]`, `[S9]`, `[A4]`, `[N12-DATE]` (`tests/e2e/50-money-and-errors.e2e.ts`), все красные и в `known-defects.json` |
| §4 | Приёмка `antigravity/fix-reject-device-shifts`, `codex/fix-format-baseline`, `codex/fix-live-database` — таблицы ниже |
| — | Синтетическая ставка 123,45 → 123,00 ₽/ч: расчёт округляет строку до рубля (`rounding_unit_minor`), тест не должен зависеть от округления (выяснилось на fix-live-database) |
## Круг 3 (TASK-GROK-3)
Ветка продолжена поверх `ba59a48` без переписывания истории (без rebase на `b0243d5`, без force-push).
| Пункт | Что сделано |
| ----- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| п.1 | `scripts/test/accept-branch.mjs <ветка>…`: worktree на ветку в `../krona-staff-acceptance/` + оверлей текущего стенда (`tests/e2e/**`, `scripts/test/**`), `npm run check` без переменных БД, **каждый** `tests/integration/*.test.ts` отдельно на свежей мигрированной БД PG17 (красный — по строке `Test Files`, ловит ошибки `beforeAll/afterAll`; файл, где всё пропущено, — отдельный вердикт `SKIP`), e2e и разница с основой по ID, попарный `git merge-tree --write-tree` (все ветки между собой и с основой), итог в markdown. Логи — `tests/e2e/evidence/accept/<время>/` (пароли в URL заменены на `***`) |
| п.1 | Готовые роли: если заданы `E2E_MIGRATOR_URL` и `E2E_APP_URL`, `global-setup` и `accept-branch` роли не создают и пароли не меняют (БД — из URL, сброс откатом всех миграций). По умолчанию как раньше. `provision-pg.mjs --roles keep` пересоздаёт только БД: `accept-branch` создаёт роли один раз на прогон |
| п.1 | Предохранитель изолированной БД для `live-postgres-remediation.test.ts` (Codex): в admin-режиме интеграционная БД одноразовая (`krona_staff_audit_acc_it`), `krona_staff_test_guard` со случайным маркером, `DATABASE_TEST_ISOLATED=true` — без этого файл пропускается целиком |
| п.2 | «Форматирование без поведения» (для веток с `format` в имени и `--format-check <ветка>`): версия из merge-base → prettier (конфиг основы, API из `node_modules`) → сравнение с веткой; для лишних строк — сколько из них есть в других переданных ветках |
| п.3 | Прогон по всем открытым `codex/*`, `antigravity/*` — ниже |
| п.4 | Храповик **не тронут**: `origin/codex/bootstrap-platform-foundation` = `b0243d5` (только docs поверх `f5ac179`); `git merge-base --is-ancestor origin/<ветка> origin/codex/bootstrap-platform-foundation` — `NOT merged` для fix-live-database, fix-reject-device-shifts, fix-sync-c0-bootstrap, fix-format-baseline |
| п.5 | `[PILOT-REHOME-SEED]` (зелёный) и `[PILOT-REHOME]` (красный, в `known-defects.json`) — `tests/e2e/60-pilot-rehome.e2e.ts` |
| — | `run-e2e.mjs`: проверка секретов в `evidence/` больше не падает `EISDIR` на подкаталоге `accept/` |
### п.1: ожидания владельца на SHA из задания
Команда: `node scripts/test/accept-branch.mjs 1df2019 antigravity/fix-sync-c0-bootstrap codex/fix-format-baseline`
(16:04 NOVT; `1df2019` передан SHA, потому что ветка Antigravity к этому времени была перезаписана
на `6c5bf36`). Полный вывод — [`report-evidence/round3-accept-owner-shas.md`](report-evidence/round3-accept-owner-shas.md).
| Ожидание владельца | Фактический вывод |
| ----------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| sync-rejection.test.ts на 1df2019 FAIL `column "created_by" … does not exist` | `FAIL \| 1 failed (1) \| 2 skipped (2) \| error: column "created_by" of relation "staff_employees" does not exist` |
| sync-bootstrap.test.ts на 8baad55 FAIL `Called end on pool more than once` при `Tests 2 passed` | `FAIL \| 1 failed (1) \| 2 passed (2) \| Error: Called end on pool more than once` |
| SYNC-C0 зелёный на 8baad55 | e2e «стало зелёным»: `SYNC-C0` |
| AG reject + AG c0 — конфликт в src/domain/sync/service.ts | `1df2019 \| origin/antigravity/fix-sync-c0-bootstrap \| КОНФЛИКТ \| src/domain/sync/service.ts` |
| Codex format + AG c0 — конфликт | `origin/antigravity/fix-sync-c0-bootstrap \| origin/codex/fix-format-baseline \| КОНФЛИКТ \| src/domain/sync/service.ts` |
| Codex format + AG reject — чисто | `1df2019 \| origin/codex/fix-format-baseline \| чисто` |
Все шесть совпали. На `6c5bf36` (первый прогон по именам веток,
[`round3-accept-owner-branches-first.md`](report-evidence/round3-accept-owner-branches-first.md))
`sync-rejection.test.ts` уже зелёный (`1 passed (1) | 2 passed (2)`).
### п.2: форматирование без поведения
Команда: `node scripts/test/accept-branch.mjs --format-check tmp/prettier-only-f5ac179 --format-check codex/fix-format-baseline`
([`round3-format-proof.md`](report-evidence/round3-format-proof.md)).
`tmp/prettier-only-f5ac179` (`c552d38`) — локальная синтетическая ветка: `prettier --write .` поверх
`f5ac179`, 24 файла; **не запушена**.
| Ветка | Результат |
| ---------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `tmp/prettier-only-f5ac179` (c552d38) | изменено файлов: 24 — ТОЛЬКО ФОРМАТИРОВАНИЕ |
| `codex/fix-format-baseline` (513cdff, прежняя голова) | ЕСТЬ ИЗМЕНЕНИЯ СВЕРХ PRETTIER: `src/domain/sync/service.ts` 232 строки, `tests/unit/sync.test.ts` 130 строк; в `antigravity/fix-reject-device-shifts` есть 232/232 и 130/130 этих строк |
| `codex/fix-format-baseline` (a05777c, текущая, 16:09 NOVT) | изменено файлов: 19 — ТОЛЬКО ФОРМАТИРОВАНИЕ |
### п.3: все открытые ветки
Команда: `node scripts/test/accept-branch.mjs <все origin/codex/* и origin/antigravity/* кроме основы>`
после `git fetch` в 16:16 NOVT, стенд `6f408bc`. Полный вывод (интеграционные тесты по файлам, 55 пар
merge-tree) — [`report-evidence/round3-accept-all-open.md`](report-evidence/round3-accept-all-open.md).
Основа `b0243d5`: check OK, integration 3/4 (FAIL tenant-isolation), e2e 46 red / 5 green.
| Ветка | SHA | Влита в основу | check | integration | e2e red/green | Стало зелёным | Стало красным |
| ------------------------------------ | ------- | -------------------------- | ----- | --------------------------------------------------------- | ------------- | ------------------------------- | -------------------------------------------------------------------------- |
| antigravity/fix-reject-device-shifts | b283736 | нет (+1) | OK | 4/5, FAIL tenant-isolation | 44/7 | K13/SYNC-A, K13/SYNC-B | — |
| antigravity/fix-sync-c0-bootstrap | 24cc18d | нет (+3, включает b283736) | OK | 5/6, FAIL tenant-isolation | 43/8 | K13/SYNC-A, K13/SYNC-B, SYNC-C0 | — |
| codex/fix-format-baseline | a05777c | нет (+1, от f5ac179) | OK | 3/4, FAIL tenant-isolation | 46/5 | — | — |
| codex/fix-live-database | 1573860 | нет (+2, на a05777c) | OK | 4/5, FAIL tenant-isolation (live-postgres-remediation OK) | 44/7 | K3/N2, K4/N1 | — |
| antigravity/employee-notifications | 3e20544 | да (предок) | OK | 3/4 | 49/2 | — | HARNESS, PILOT-REHOME-SEED, SYNC-L (старый код: нет таблиц/маршрутов sync) |
| antigravity/offline-sync-backend | 5ee7517 | да (предок) | OK | 3/4 | 46/5 | — | — |
| codex/finance-calendar-dashboard | c08a7e6 | да (предок) | OK | 4/4 | 48/3 | A4, K4/N1 | CTRL, HARNESS, PILOT-REHOME-SEED, SYNC-L (старый код) |
| codex/frontend-library | 331e490 | да (предок) | OK | 3/4 | 46/5 | — | — |
| codex/security-finance-production | f74aa6d | да (предок) | OK | 4/4 | 49/2 | — | HARNESS, PILOT-REHOME-SEED, SYNC-L (старый код) |
| codex/server-web-production | 2f6b367 | да (предок) | OK | 4/4 | 49/2 | K4/N1 | CTRL, HARNESS, PILOT-REHOME-SEED, SYNC-L (старый код) |
- `git merge-tree`: **конфликтов нет** ни в одной из 55 пар (Antigravity выстроил цепочку reject → c0,
Codex пересобрал format без sync). Шесть веток — предки основы (уже влиты), для них «стало
красным» означает только «код старше основы».
- `tenant-isolation.test.ts` красный на основе и всех ветках от `f5ac179`
(`error: permission denied for table staff_employees`) — это N5, исправление есть в
`grok/e2e-harness` (`ba59a48`), в основу ещё не влито.
### Сборка «как будет после слияния» (локально, не запушено)
`tmp/combined-round3` = `b0243d5` + merge `codex/fix-live-database` (1573860) + merge
`antigravity/fix-sync-c0-bootstrap` (24cc18d) — без конфликтов; `tmp/combined-round3-grok` = то же +
merge `grok/e2e-harness`. Вывод — [`round3-combined-merge.md`](report-evidence/round3-combined-merge.md).
| Сборка | check | integration | e2e red/green | Стало зелёным |
| ------------------------------ | -------------------------- | -------------------------- | ------------- | --------------------------------------------- |
| combined-round3 (48128c8) | OK (199 passed, 6 skipped) | 6/7, FAIL tenant-isolation | 41/10 | K13/SYNC-A, K13/SYNC-B, K3/N2, K4/N1, SYNC-C0 |
| combined-round3-grok (e94415a) | OK (199 passed, 6 skipped) | **7/7** | 41/10 | то же |
Храповик на `combined-round3-grok`: `[run-e2e] FAIL: K13/SYNC-A, K13/SYNC-B, K3/N2, K4/N1, SYNC-C0 —
дефект исправлен, обновите known-defects.json` — ровно список из TASK-GROK-3 п.4. Удалю их отдельным
коммитом, когда ветки действительно окажутся в основе.
### п.5: PILOT-REHOME
- `[PILOT-REHOME-SEED]` — **зелёный**: 27 таблиц с `tenant_id` из `information_schema.columns`, в каждую
2+ строки заглушки `01J00000000000000000000000` и постороннего случайного арендатора (SQL мигратором,
`app.tenant_id`/`app.user_id` в транзакции); таблица без сида → красный со списком.
- `[PILOT-REHOME]` — **красный** (в `known-defects.json`): `scripts/rehome-tenant.mjs отсутствует
(TASK-CODEX-2 п.2.2): … Засеяно таблиц: 27, ledger SUM(amount_minor)=…`.
- Сценарий проверен временным эталоном скрипта (не коммитился, удалён): с ним зелёный; мутации
«без записи аудита», «без отказа на повтор», «пропущена таблица», «dry-run пишет» — красные
([`report-evidence/round3-pilot-rehome.txt`](report-evidence/round3-pilot-rehome.txt)).
- Контракт, который проверяет сценарий, — `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
export E2E_PG_ADMIN_URL=postgres://<admin>:<pw>@127.0.0.1:55433/postgres # только создание ролей и БД
E2E_MIGRATIONS=roundtrip node scripts/test/run-e2e.mjs # храповик known-defects (как в CI)
node scripts/test/run-e2e.mjs --no-known-defects # строго: любой красный = exit 1
./scripts/test/ci-local.sh # локальный эквивалент verify/format/e2e (bash)
node scripts/test/accept-branch.mjs <ветка> [<ветка>…] # приёмка веток (worktree, check, integration, e2e, merge-tree)
# роли уже есть (общий кластер): E2E_MIGRATOR_URL=… E2E_APP_URL=… — роли и пароли не трогаются
```
## Таблица по ID (основная ветка f5ac179 + этот стенд)
Команда: `./scripts/test/ci-local.sh` (внутри `E2E_MIGRATIONS=roundtrip node scripts/test/run-e2e.mjs`),
2026-09-26 ~16:25 (UTC+7), PostgreSQL 17.11, стенд круга 3:
```text
::: [check] with_db_env npm run check
Test Files 20 passed (20)
Tests 196 passed (196)
::: [format-check] FAIL exit=1 # 23 файла базовой линии основы, ни одного из tests/e2e, scripts/test, .forgejo
Test Files 6 failed | 1 passed (7)
Tests 46 failed | 10 passed (56)
[run-e2e] OK: стенд работает, все красные сценарии — известные открытые дефекты
```
Круг 3 добавил `[PILOT-REHOME-SEED]` (GREEN) и `[PILOT-REHOME]` (RED, `scripts/rehome-tenant.mjs
отсутствует (TASK-CODEX-2 п.2.2)`); остальные строки таблицы — без изменений со второго круга.
| ID владельца / мой | Цвет | Фактический вывод (первая строка assert) |
| ---------------------------- | ----- | ------------------------------------------------------------------------------------------------- |
| HARNESS (×5), CTRL (×2) | GREEN | 7/7 passed — стенд и позитивные контроли |
| K1/F2 | RED | `{ portal: 200, payroll: 200, write: 201 }` вместо 401 |
| K1/SYNC-I | RED | анонимная регистрация устройства `HTTP 201` |
| K1/PILOT-1 | RED | 14 подстановок без `:?` (первая `STANDALONE_MANAGER_TOKEN`) |
| K2/PILOT-2 | RED | `NODE_ENV=development` у сервиса `app` |
| K3/N2 | RED | подтверждение смены `HTTP 500 code 23502 null value in column "occurred_at" of "staff_outbox"` |
| K4/N1 | RED | `HTTP 500 code 42P08 inconsistent types deduced for parameter $2` |
| K5/N4 | RED | блокирован K3 (500 на подтверждении) |
| K6/D5 | RED | блокирован K3 (500 на подтверждении) |
| K7/D2 | RED | `afterFirst: 22200, afterSecond: 22200` вместо `33300, 37700` |
| K8/D1 | RED | блокирован K3 (500 на подтверждении) |
| K9/D3 | RED | `params.shift_rate_minor` не сохранён (`HTTP 200`, в `params` нет ключа) |
| K10/F3 | RED | `rawTokenInCookie: true` |
| K11/F4 | RED | все попытки `401`, 429 нет |
| K12/N3 | RED | подписка на `https://127.0.0.1:<port>` `HTTP 201`, 1 входящее соединение |
| K13/SYNC-A, K13/SYNC-B | RED | `upsert_work_session` / `upsert_timesheet_entry` → `status: applied` |
| K17/OWN-10, K17/OWN-11 | RED | `404 Route GET .../terminal/employees/:id/enrollment` / `PUT .../biometric-consent` (нет API, C3) |
| K19/OWN-07 | RED | `404 Route POST /access-grants` (нет API, C1/C2) |
| D6/OWN-01, D6/OWN-02 | RED | `404 Route POST /access-grants` (нет API, C1) |
| OWN-03..06, OWN-08, OWN-09 | RED | `404 Route POST /access-grants` (нет API, C1/C2) |
| S2/SYNC-J | RED | отметка 2001 года → `status: applied` |
| S3/SYNC-G | RED | `HTTP 500 code 22007` на весь пакет |
| S4/SYNC-E | RED | отметка уволенного → `applied` |
| S4/SYNC-F | RED | `{ ownOrgUnit: null, foreign: "applied" }` |
| S5/SYNC-C | RED | `HTTP 500 code 42703 column "status" does not exist` (блокирован SYNC-C0) |
| S5/SYNC-D | RED | изменение из портала не видно в инкрементальном pull |
| S8/SYNC-H | RED | регистрация устройства по cookie с чужим Origin `HTTP 201` |
| S9 | RED | удаление точки с устройством: `23502 null value in column "tenant_id"` |
| A4 | RED | 2 тела 500 с SQLSTATE/текстом PostgreSQL (`POST /employees` 42P08, `month=2026-13` 22008) |
| N9 | RED | чужой push-endpoint перепривязан `HTTP 201` |
| N10 | RED | терминал: `{ professions: 200, units: 200 }` |
| N11 | RED | импорт с неверным kind `HTTP 500 code 23514` |
| N12 | RED | лишнее поле и нереальная сумма → `HTTP 200` |
| N12-DATE | RED | `month=2026-13` → `HTTP 500 code 22008`; `2026-02-30` уже 400 |
| F7 | RED | повтор Idempotency-Key создал второе событие |
| F10 | RED | мутация по cookie без Origin `HTTP 201` |
| SYNC-C0 (нет ID у владельца) | RED | bootstrap pull `HTTP 500 code 42703 column "status" does not exist` |
| SYNC-K (нет ID у владельца) | RED | ack курсора из будущего `HTTP 200` |
| SYNC-L | GREEN | отозванное устройство получает 403 (позитивный контроль) |
| PILOT-REHOME-SEED | GREEN | 27 таблиц с `tenant_id` засеяны (заглушка + посторонний арендатор) |
| PILOT-REHOME | RED | `scripts/rehome-tenant.mjs отсутствует (TASK-CODEX-2 п.2.2)` |
| PILOT-3 | GREEN | у `app` нет учётных данных мигратора |
| N5 (tenant-isolation) | GREEN | в `npm run check` с тестовой БД: `Test Files 20 passed (20)`, `Tests 196 passed (196)` |
| N13 (скрипт, не vitest) | — | не воспроизведено: 0 из 30 попыток при 10/25/50 (обход K3 в одноразовой БД), см. ниже |
Сверх таблицы TASK-GROK-2 §2 (по тексту AUDIT.md): K1/SYNC-I (анонимный доступ без токенов на
`/sync`), K1/PILOT-1 (подстановки без `:?` названы в K1), K2/PILOT-2 (development в пилоте — суть K2).
## Приёмка веток исполнителей (TASK-GROK-2 §4)
Способ: `git worktree add --detach <dir> origin/<ветка>`, поверх — `tests/e2e/` и `scripts/test/`
из этой ветки (в ветки исполнителей ничего не коммитилось), затем
`E2E_MIGRATIONS=roundtrip E2E_DATABASE_NAME=krona_staff_acc node scripts/test/run-e2e.mjs`.
«Было» — основная ветка `f5ac179` с тем же стендом (45 red / 9 green, храповик OK). Полные таблицы —
`tests/e2e/report-evidence/acceptance-*.txt`. Ни одна ветка не влита в основную
(`git merge-base --is-ancestor` → нет), поэтому `known-defects.json` не сокращён.
### `codex/fix-live-database` @ ea68e12, перепроверено на b289dd6
```text
== roundtrip OK: rolledBack=11 reapplied=11
Tests 41 failed | 13 passed (54)
| K13/SYNC-A | GREEN | 1/1 | зелёный — ИСПРАВЛЕН, уберите из known-defects.json |
| K13/SYNC-B | GREEN | 1/1 | зелёный — ИСПРАВЛЕН, уберите из known-defects.json |
| K3/N2 | GREEN | 1/1 | зелёный — ИСПРАВЛЕН, уберите из known-defects.json |
| K4/N1 | GREEN | 1/1 | зелёный — ИСПРАВЛЕН, уберите из known-defects.json |
```
| ID | Было | Стало | Доказательство |
| ---------- | ---- | ----- | ------------------------------------------------------------------------------------------------------------ |
| K3/N2 | RED | GREEN | было: подтверждение смены `500 23502 occurred_at`; стало: passed (8 ч × 123,00 ₽ = 98400 в ledger) |
| K4/N1 | RED | GREEN | было: `500 42P08`; стало: passed (201, сотрудник в БД) |
| K13/SYNC-A | RED | GREEN | было: `upsert_work_session → applied`; стало: passed (см. находку по fd1ec3d) |
| K13/SYNC-B | RED | GREEN | было: `upsert_timesheet_entry → applied`; стало: passed |
| K5/N4 | RED | RED | было: блокирован K3; стало: `смена должна быть начислена ровно один раз: expected 196800n to be 98400n` |
| K6/D5 | RED | RED | было: блокирован K3; стало: `после отклонения оплаченной смены должно быть сторно: expected 98400n to be 0n` |
| K8/D1 | RED | RED | было: блокирован K3; стало: `C4 verify: HTTP 404 Route GET .../payroll/runs/:id/verify not found` |
| A4 | RED | RED | было: 2 утечки; стало: 1 — `POST /payroll/calculate month=2026-13` → `code=22008` |
| F14 | RED | GREEN | `prettier --check .` → `All matched files use Prettier code style!` (без оверлея evidence) |
Ветку обновили force-push во время приёмки (`ea68e12` → `b289dd6`, изменён только
`tests/integration/live-postgres-remediation.test.ts`); повторный прогон на `b289dd6` дал те же
41 red / 13 green и ту же таблицу.
Вердикт: K3 и K4 исправлены (подтверждено на живой PostgreSQL 17, миграции roundtrip). После влития
убрать `K3/N2`, `K4/N1` из `known-defects.json`. Цепочка расчёта теперь доходит до денежных дефектов:
K5 и K6 подтверждены по существу.
### `codex/fix-format-baseline` @ fd1ec3d
```text
Tests 43 failed | 11 passed (54)
| K13/SYNC-A | GREEN | 1/1 | зелёный — ИСПРАВЛЕН, уберите из known-defects.json |
| K13/SYNC-B | GREEN | 1/1 | зелёный — ИСПРАВЛЕН, уберите из known-defects.json |
$ node node_modules/prettier/bin/prettier.cjs --check .
All matched files use Prettier code style!
```
| ID | Было | Стало | Доказательство |
| ---------- | -------------- | ----- | ------------------------------------------------- |
| F14 | RED (23 файла) | GREEN | `All matched files use Prettier code style!` |
| K13/SYNC-A | RED | GREEN | было: `applied`; стало: passed |
| K13/SYNC-B | RED | GREEN | было: `applied`; стало: passed |
| остальные | без изменений | — | compare по `e2e-results.json`: других отличий нет |
**Находка:** «style»-коммит `fd1ec3d` содержит функциональное исправление Antigravity.
После нормализации prettier `src/domain/sync/service.ts` отличается от `331e490` на 255 строк diff
и совпадает с `antigravity/fix-reject-device-shifts` (0 строк); `tests/unit/sync.test.ts` — 139 и 0.
Изменение поведения скрыто в коммите форматирования — смешение тем (AGENTS.md).
### `antigravity/fix-reject-device-shifts` @ 795bba0, перепроверено на 1df2019
```text
Tests 43 failed | 11 passed (54)
| K13/SYNC-A | GREEN | 1/1 | зелёный — ИСПРАВЛЕН, уберите из known-defects.json |
| K13/SYNC-B | GREEN | 1/1 | зелёный — ИСПРАВЛЕН, уберите из known-defects.json |
```
| ID | Было | Стало | Доказательство |
| ---------- | ---- | ----- | ------------------------------------------------------- |
| K13/SYNC-A | RED | GREEN | было: `upsert_work_session → applied`; стало: passed |
| K13/SYNC-B | RED | GREEN | было: `upsert_timesheet_entry → applied`; стало: passed |
| остальные | — | — | других отличий нет |
Ветку перебазировали на `f5ac179` во время приёмки (`795bba0` → `1df2019`); повторный прогон
на `1df2019`: те же 43 red / 11 green, отличия только K13/SYNC-A/B.
Вердикт: K13 исправлен. Убрать `K13/SYNC-A`,
`K13/SYNC-B` отдельным коммитом после влития (и после исправления очистки в
`sync-rejection.test.ts`, TASK-GROK-2 §4.1).
## Windows
- Запуск стенда переписан без `npx` и `node_modules/.bin` (раньше `spawn('npx')` и
`spawn('.../.bin/tsx')` падали на Windows с `ENOENT`). Проверка на Linux:
`tests/e2e/harness/node-commands.test.ts` — 8 тестов: команды начинаются с `process.execPath`,
без `.cmd`/`.bin`/`npx`, файлы существуют; под `win32` дочерний процесс получает
`SystemRoot`/`TEMP`, секреты родителя не наследуются.
- **Реальный запуск на Windows 11 — не проверено**: Windows-раннера у git.ochenstarik.ru нет.
В CI добавлена задача `windows` (`runs-on: windows`), включаемая переменной
`KRONA_WINDOWS_RUNNER=true`; для полного e2e нужен секрет `WINDOWS_E2E_PG_ADMIN_URL`.
- `scripts/test/ci-local.sh` — bash, на Windows не предназначен (под Windows запускать
`node scripts/test/run-e2e.mjs`).
## Evidence вне git
```text
$ E2E_MIGRATIONS=roundtrip node scripts/test/run-e2e.mjs # (повторный прогон)
[run-e2e] OK: стенд работает, все красные сценарии — известные открытые дефекты
$ git status --porcelain
# (пусто)
```
## N5 (tenant-isolation)
До (`778ba2d`, очистка ролью приложения) — [`report-evidence/n5-before.txt`](report-evidence/n5-before.txt):
`error: permission denied for table staff_employees`, `exit=1`. После (очистка ролью мигратора с
`app.tenant_id`) — [`report-evidence/n5-after.txt`](report-evidence/n5-after.txt): 2 прогона, `exit=0`.
На `f5ac179`: `npm run check` с тестовой БД — `Test Files 20 passed (20)`, `Tests 196 passed (196)`.
## CI (`.forgejo/workflows/ci.yml`)
- `verify`: `postgres:17-alpine@sha256:…`, роли как в pilot (мигратор NOSUPERUSER, app NOBYPASSRLS),
миграции `up → откат всех 11 → up → up`, `npm run check` с тестовой БД, `npm audit`.
- `format`: отдельно (F14 красный до влития `codex/fix-format-baseline`).
- `e2e`: PostgreSQL 17, `E2E_MIGRATIONS=roundtrip`, храповик; evidence — артефакт `e2e-evidence`
(`actions/upload-artifact@ea165f8… # v4.6.2`, Forgejo 9.0.3).
- `windows`: выключена без раннера (см. выше). `docker-build`: метка `docker-host`, `needs: verify`, только push.
- Все actions закреплены SHA (F13), SHA совпадают на github.com и code.forgejo.org.
Механизм красных сценариев — храповик `known-defects.json`, а не «allowed to fail»: сломанный стенд,
новый красный ID или исправленный, но не вычеркнутый ID делают задачу `e2e` красной.
Валидация: `actionlint -config-file actionlint.yaml .forgejo/workflows/ci.yml` (1.7.12, метки
`docker`, `docker-host`, `windows`) → 0 замечаний. Локально `./scripts/test/ci-local.sh`:
```text
::: [migrate-roundtrip] OK (== roundtrip OK: rolledBack=11 reapplied=11)
::: [check] OK (Test Files 20 passed (20), Tests 196 passed (196))
::: [audit] OK (found 0 vulnerabilities)
::: [format-check] FAIL (Code style issues found in 23 files — F14)
::: [e2e] OK (Tests 45 failed | 9 passed (54), храповик OK)
```
## N13 (гонка первого расчёта)
`node scripts/test/n13-race.mjs` (10/25/50 параллельных первых расчётов × 5 попыток) —
[`report-evidence/n13-race.txt`](report-evidence/n13-race.txt): как есть 15/15 → 500 (K3); с обходом K3
в одноразовой БД — 0 некорректных из 30 (с подтверждённой сменой и без). Не воспроизведено, как и у
владельца; вероятная причина (гипотеза по коду) — upsert `ensureDefaultPayRules` сериализует расчёты
тенанта. Прогон до перебазирования, на `778ba2d`.
## Не проверено
- Реальный Forgejo-раннер (workflow выполнялся только локальным эквивалентом), `docker build`.
- Запуск на Windows 11 (раннера нет); Windows-only: DPAPI, ACL каталога данных, установщик, WinUI,
named pipe — см. тесты Codex в `apps/KronaStaffTerminal.Tests`.
- OWN-01..11, K8 (C4) и K9 (C5) проверяют предполагаемые контракты из NOTES.
- K5/N4, K6/D5, K8/D1 на основной ветке блокированы K3; по существу проверены только на
`codex/fix-live-database`.
- S9 проверен удалением SQL-запросом мигратора (публичного API удаления точки нет).
- Причина невоспроизводимости N13 — гипотеза по коду.
- Круг 3: в основе `scripts/rehome-tenant.mjs` нет; на `codex/fix-tenant-rehome` скрипт проверен на копии
и через диагностический адаптер (интерфейс не совпадает с C6). `accept-branch` в CI не запускается (разовый инструмент
владельца/приёмщика). Режим `E2E_MIGRATOR_URL/E2E_APP_URL` проверен на том же локальном кластере
(роли созданы заранее `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 сотрудник) не перепроверялось — данных пилота у меня нет.