docs(e2e): REPORT.md второго круга — двойные ID, приёмка веток, Windows

Зачем: PR в Forgejo создаётся без API, описание изменений и доказательства
живут в репозитории; владелец просил обновлённый отчёт с таблицей
«ID владельца / мой ID — цвет — фактический вывод» и приёмочными таблицами
по веткам исполнителей (TASK-GROK-2, раздел «Отчёт»).

Что: что изменилось в круге 2, таблица по всем ID на f5ac179 (45 red /
9 green, храповик OK), приёмка fix-live-database (K3, K4 зелёные; K5, K6
подтверждены по существу), fix-format-baseline (F14 зелёный; находка —
«style»-коммит содержит исправление Antigravity), fix-reject-device-shifts
(K13 зелёный), статус Windows (не проверено: нет раннера), CI, N5, N13,
список «не проверено». Ссылки на evidence ведут в report-evidence/.
This commit is contained in:
grok 2026-09-26 15:29:36 +07:00
parent 51ad03ff65
commit 33daae3593

View file

@ -1,225 +1,249 @@
# PR: e2e-стенд на реальной PostgreSQL 17 и независимая приёмка (grok/e2e-harness) # PR: e2e-стенд на реальной PostgreSQL 17 и независимая приёмка (grok/e2e-harness)
База: `codex/bootstrap-platform-foundation` @ `778ba2d`. Сравнение: База: `codex/bootstrap-platform-foundation` @ `f5ac179` (содержит `331e490`). Ветка перебазирована
без конфликтов. Сравнение:
<https://git.ochenstarik.ru/ochenstarik/krona-staff/compare/codex/bootstrap-platform-foundation...grok/e2e-harness> <https://git.ochenstarik.ru/ochenstarik/krona-staff/compare/codex/bootstrap-platform-foundation...grok/e2e-harness>
Изменены только `tests/e2e/**`, `tests/integration/tenant-isolation.test.ts`, Изменены только `tests/e2e/**`, `tests/integration/tenant-isolation.test.ts`,
`.forgejo/workflows/ci.yml`, `scripts/test/**`. `src/`, `apps/`, `migrations/`, `deploy/`, `.forgejo/workflows/ci.yml`, `scripts/test/**` и одна строка в корневом `.gitignore`
`package.json` не тронуты. Всё, что требует правки продукта, — в (`tests/e2e/evidence/`, по требованию TASK-GROK-2 §1). `src/`, `apps/`, `migrations/`,
`deploy/`, `package.json` не тронуты. Всё, что требует правки продукта, — в
[`NOTES-FOR-EXECUTORS.md`](NOTES-FOR-EXECUTORS.md). [`NOTES-FOR-EXECUTORS.md`](NOTES-FOR-EXECUTORS.md).
## Зачем ## Зачем
156 зелёных тестов на поддельной БД не заметили, что создание сотрудника, 156 зелёных тестов на поддельной БД не заметили, что создание сотрудника, подтверждение
подтверждение смены и расчёт зарплаты на реальной PostgreSQL падают всегда. Этот PR смены и расчёт зарплаты на реальной PostgreSQL падают всегда. Стенд запускает реальный сервер
добавляет стенд, где реальный сервер (`tsx src/server.ts`) работает с реальной под ролью приложения без привилегий на реальной PostgreSQL 17, миграции применяет мигратор без
PostgreSQL 17 под ролью приложения без привилегий, миграции применяет мигратор без SUPERUSER, все токены и пароли случайные (`crypto.randomBytes`). Каждый сценарий проверяет
SUPERUSER, а все токены и пароли случайные (`crypto.randomBytes`). Каждый сценарий **правильное** поведение: красный = открытый дефект.
проверяет **правильное** поведение, поэтому красный цвет означает открытый дефект.
## Источник ID ## Что изменилось во втором круге (TASK-GROK-2)
Файлы владельца (`AUDIT.md`, `live-audit.mjs`, `live-sync.mjs`) до конца работы в | Пункт | Что сделано |
`/workspace/uploads/krona-staff-owner/` не появились. Сценарии написаны по моему | ----- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
аудиту `2026-09-26_full-project-audit_grok_3e20544.md` (ID `F*`/`N*`) и по коду. ID владельца | §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`) |
K1, K3, K4, K5, K6, K10, K11, K12, S1, S2, S9, A4 **не сопоставлены — не проверено**. | §1 | `tests/e2e/evidence/` убран из git и добавлен в `.gitignore`; в CI — артефакт `e2e-evidence`. Разовые доказательства (N5, N13, приёмка) — в `tests/e2e/report-evidence/`, скрипты туда не пишут |
Синхронизация помечена `SYNC-*` (мои ID, вероятно пересекаются с S1–S9 владельца). | §1 | Задача `windows` в CI (включается переменной `KRONA_WINDOWS_RUNNER=true`, раннера пока нет) |
Когда файлы владельца будут доступны, достаточно переименовать теги в названиях | §1 | Перебазирование на `f5ac179` — без конфликтов |
тестов (например `[K3/N1]`) и ключи в `known-defects.json`. | §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) |
| SHA | Что |
| --------- | ------------------------------------------------------------------------------------- |
| `29fefc1` | `scripts/test/provision-pg.mjs`, `migrate-roundtrip.mjs`: роли как в pilot, roundtrip |
| `88fc749` | N5: очистка tenant-isolation ролью мигратора |
| `d7e9008` | prettier для tenant-isolation.test.ts |
| `cf7f6f4` | стенд e2e: global-setup, клиент, фикстуры, храповик `run-e2e.mjs` |
| `518494d` | сценарии основного API (N1–N4, N9–N12, F2–F4, F7, F10) |
| `32ced4b` | сценарии `/sync/*` (SYNC-A…L) |
| `9bdfb66` | форматирование сгенерированных evidence |
| `f59be3a` | CI: PostgreSQL 17, роли, roundtrip, e2e, docker-build отдельно, SHA-пины |
| `a19c06b` | проверка `deploy/pilot/compose.yaml` (PILOT-1..3) |
| `1e325a6` | сценарии решений владельца (OWN-01..11) + заметки исполнителям |
| `3388b90` | N13: гонка первого расчёта (скрипт и замер) |
| (этот) | REPORT.md и свежие evidence |
## Как запустить ## Как запустить
```sh ```sh
# PostgreSQL 17, bootstrap-админ нужен только для создания ролей и БД export E2E_PG_ADMIN_URL=postgres://<admin>:<pw>@127.0.0.1:55433/postgres # только создание ролей и БД
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 # храповик known-defects (как в CI)
node scripts/test/run-e2e.mjs --no-known-defects # строго: любой красный = exit 1 node scripts/test/run-e2e.mjs --no-known-defects # строго: любой красный = exit 1
E2E_MIGRATIONS=roundtrip node scripts/test/run-e2e.mjs ./scripts/test/ci-local.sh # локальный эквивалент verify/format/e2e (bash)
./scripts/test/ci-local.sh # локальный эквивалент всех задач CI
``` ```
Роли (`provision-pg.mjs`): `krona_migrator` — NOSUPERUSER NOCREATEDB NOCREATEROLE, ## Таблица по ID (основная ветка f5ac179 + этот стенд)
владелец БД и схемы `public`; `krona_staff_app` — NOSUPERUSER NOBYPASSRLS, права выдают
миграции 0003/0004. Пароли случайные, пишутся в env-файл 0600 во временном каталоге,
который удаляется после запуска.
## Таблица green/red по ID Команда: `./scripts/test/ci-local.sh` (внутри `E2E_MIGRATIONS=roundtrip node scripts/test/run-e2e.mjs`),
2026-09-26 ~15:28 (UTC+7), PostgreSQL 17.11:
Команда: `E2E_MIGRATIONS=roundtrip node scripts/test/run-e2e.mjs` (в составе
`./scripts/test/ci-local.sh`), 2026-09-26 14:33 (UTC+7), PostgreSQL 17.11. Фактический вывод:
[`evidence/e2e-run.txt`](evidence/e2e-run.txt), [`evidence/ci-local.txt`](evidence/ci-local.txt),
JSON — [`evidence/e2e-results.json`](evidence/e2e-results.json), логи серверов — `evidence/server-*.txt`.
```text ```text
Test Files 4 failed | 1 passed (5) == roundtrip OK: rolledBack=11 reapplied=11
Tests 38 failed | 9 passed (47) Test Files 5 failed | 1 passed (6)
Tests 45 failed | 9 passed (54)
[run-e2e] OK: стенд работает, все красные сценарии — известные открытые дефекты [run-e2e] OK: стенд работает, все красные сценарии — известные открытые дефекты
``` ```
| ID | Цвет | Что проверяется (правильное поведение) | Фактически (из вывода) | | ID владельца / мой | Цвет | Фактический вывод (первая строка assert) |
| ------------ | ----- | -------------------------------------------------------------------- | ----------------------------------------------------------------- | | ---------------------------- | ----- | ------------------------------------------------------------------------------------------------- |
| HARNESS (×5) | GREEN | live/ready, PG17, app-роль без SUPERUSER/BYPASSRLS, токены, терминал | 5/5 passed | | HARNESS (×5), CTRL (×2) | GREEN | 7/7 passed — стенд и позитивные контроли |
| CTRL (×2) | GREEN | RBAC терминала/сотрудника, уволенный теряет доступ | 2/2 passed | | K1/F2 | RED | `{ portal: 200, payroll: 200, write: 201 }` вместо 401 |
| N1 | RED | POST /employees → 201 | 500 `42P08 inconsistent types deduced for parameter $2` | | K1/SYNC-I | RED | анонимная регистрация устройства `HTTP 201` |
| N2 | RED | подтверждение смены, расчёт, утверждение → 200 | 500 `23502 null value in column "occurred_at" of "staff_outbox"` | | K1/PILOT-1 | RED | 14 подстановок без `:?` (первая `STANDALONE_MANAGER_TOKEN`) |
| N4 | RED | пересекающиеся расчёты не начисляют смену дважды | блокирован N2 (500 23502 на предусловии) — сам N4 **не проверен** | | K2/PILOT-2 | RED | `NODE_ENV=development` у сервиса `app` |
| N3 | RED | push на внутренний адрес отклоняется, соединений нет | 201 и 1 входящее соединение на 127.0.0.1 | | K3/N2 | RED | подтверждение смены `HTTP 500 code 23502 null value in column "occurred_at" of "staff_outbox"` |
| N9 | RED | чужой push-endpoint не перепривязывается | 201 | | K4/N1 | RED | `HTTP 500 code 42P08 inconsistent types deduced for parameter $2` |
| N10 | RED | терминал не читает справочники (403) | professions 200, org-units 200 | | K5/N4 | RED | блокирован K3 (500 на подтверждении) |
| N11 | RED | импорт с неверным kind → 400 | 500 `23514` check constraint staff_payroll_ledger | | K6/D5 | RED | блокирован K3 (500 на подтверждении) |
| N12 | RED | лишние поля/нереальные суммы → 400 | 200 | | K7/D2 | RED | `afterFirst: 22200, afterSecond: 22200` вместо `33300, 37700` |
| F2 | RED | процесс без токенов не даёт прав анониму (401) | /portal 200, /payroll 200, POST /professions 201 | | K8/D1 | RED | блокирован K3 (500 на подтверждении) |
| F3 | RED | cookie без сырого токена, logout отзывает | `rawTokenInCookie: true` | | K9/D3 | RED | `params.shift_rate_minor` не сохранён (`HTTP 200`, в `params` нет ключа) |
| F4 | RED | 429 не позже 30 неверных входов | все 401, 429 нет | | K10/F3 | RED | `rawTokenInCookie: true` |
| F7 | RED | повтор Idempotency-Key с другим телом не создаёт событие | второе событие создано (201) | | K11/F4 | RED | все попытки `401`, 429 нет |
| F10 | RED | мутация по cookie без Origin → 403 | 201 | | K12/N3 | RED | подписка на `https://127.0.0.1:<port>` `HTTP 201`, 1 входящее соединение |
| SYNC-A | RED | устройство не подтверждает смену | `applied` | | K13/SYNC-A, K13/SYNC-B | RED | `upsert_work_session` / `upsert_timesheet_entry` → `status: applied` |
| SYNC-B | RED | устройство не утверждает табель | `applied` | | K17/OWN-10, K17/OWN-11 | RED | `404 Route GET .../terminal/employees/:id/enrollment` / `PUT .../biometric-consent` (нет API, C3) |
| SYNC-C0 | RED | bootstrap pull без курсора → 200 | 500 `42703 column "status" does not exist` (новая находка) | | K19/OWN-07 | RED | `404 Route POST /access-grants` (нет API, C1/C2) |
| SYNC-C | RED | bootstrap >100 сущностей постранично без потерь | 500 42703 (блокирован SYNC-C0) | | D6/OWN-01, D6/OWN-02 | RED | `404 Route POST /access-grants` (нет API, C1) |
| SYNC-D | RED | изменения из портала видны в инкрементальном pull | изменения нет | | OWN-03..06, OWN-08, OWN-09 | RED | `404 Route POST /access-grants` (нет API, C1/C2) |
| SYNC-E | RED | отметка уволенного через sync отклоняется | `applied` | | S2/SYNC-J | RED | отметка 2001 года → `status: applied` |
| SYNC-F | RED | точка устройства сохраняется, чужая точка в payload не принимается | `applied`, точка не сохранена | | S3/SYNC-G | RED | `HTTP 500 code 22007` на весь пакет |
| SYNC-G | RED | битая команда отклоняется по отдельности | 500 `22007` на весь пакет | | S4/SYNC-E | RED | отметка уволенного → `applied` |
| SYNC-H | RED | регистрация устройства по cookie с чужим Origin → 403 | 201 | | S4/SYNC-F | RED | `{ ownOrgUnit: null, foreign: "applied" }` |
| SYNC-I | RED | без токенов анонимно устройство не регистрируется | 201 | | S5/SYNC-C | RED | `HTTP 500 code 42703 column "status" does not exist` (блокирован SYNC-C0) |
| SYNC-J | RED | отметка 2001 года через sync отклоняется | `applied` | | S5/SYNC-D | RED | изменение из портала не видно в инкрементальном pull |
| SYNC-K | RED | ack курсора из будущего → 400 | 200 | | S8/SYNC-H | RED | регистрация устройства по cookie с чужим Origin `HTTP 201` |
| SYNC-L | GREEN | отозванное устройство не синхронизируется (403) | 403 (позитивный контроль) | | S9 | RED | удаление точки с устройством: `23502 null value in column "tenant_id"` |
| OWN-01 | RED | менеджер точки A считает зарплату точки B → 403 | 404 `Route POST:/api/v1/staff/access-grants not found` (нет API) | | A4 | RED | 2 тела 500 с SQLSTATE/текстом PostgreSQL (`POST /employees` 42P08, `month=2026-13` 22008) |
| OWN-02 | RED | менеджер отправляет пакет на согласование → 403 | 404 access-grants (нет API) | | N9 | RED | чужой push-endpoint перепривязан `HTTP 201` |
| OWN-03 | RED | бухгалтер отправляет пакет → успех, каждому директору 1 уведомление | 404 access-grants (нет API) | | N10 | RED | терминал: `{ professions: 200, units: 200 }` |
| OWN-04 | RED | бухгалтер подписывает пакет → 403 | 404 access-grants (нет API) | | N11 | RED | импорт с неверным kind `HTTP 500 code 23514` |
| OWN-05 | RED | до подписи в ledger нет начислений/выплат | 404 access-grants (нет API) | | N12 | RED | лишнее поле и нереальная сумма → `HTTP 200` |
| OWN-06 | RED | подпись директора → ledger, пакет и смены неизменяемы | 404 access-grants (нет API) | | N12-DATE | RED | `month=2026-13` → `HTTP 500 code 22008`; `2026-02-30` уже 400 |
| OWN-07 | RED | смена изменена после отправки → 409, пакет у бухгалтера | 404 access-grants (нет API) | | F7 | RED | повтор Idempotency-Key создал второе событие |
| OWN-08 | RED | возврат с комментарием → пакет у бухгалтера, комментарий сохранён | 404 access-grants (нет API) | | F10 | RED | мутация по cookie без Origin `HTTP 201` |
| OWN-09 | RED | смена сотрудника A в точке B → в расчёт точки B | 404 access-grants (нет API) | | SYNC-C0 (нет ID у владельца) | RED | bootstrap pull `HTTP 500 code 42703 column "status" does not exist` |
| OWN-10 | RED | без согласия на биометрию → терминалу «регистрация запрещена» | 404 `GET .../terminal/employees/:id/enrollment` (нет API) | | SYNC-K (нет ID у владельца) | RED | ack курсора из будущего `HTTP 200` |
| OWN-11 | RED | согласие отозвано → статус для терминала меняется | 404 `PUT .../biometric-consent` (нет API) | | SYNC-L | GREEN | отозванное устройство получает 403 (позитивный контроль) |
| PILOT-1 | RED | все подстановки в pilot compose — `${VAR:?}` | 14 переменных без `:?` (первая — `STANDALONE_MANAGER_TOKEN`) | | PILOT-3 | GREEN | у `app` нет учётных данных мигратора |
| PILOT-2 | RED | нет `NODE_ENV=development` | сервис `app` | | N5 (tenant-isolation) | GREEN | в `npm run check` с тестовой БД: `Test Files 20 passed (20)`, `Tests 196 passed (196)` |
| PILOT-3 | GREEN | у `app` нет кредов мигратора | passed | | N13 (скрипт, не vitest) | — | не воспроизведено: 0 из 30 попыток при 10/25/50 (обход K3 в одноразовой БД), см. ниже |
| N13 | — | гонка первого расчёта (скрипт, не vitest) | см. ниже: как есть 15/15 → 500 (N2); с обходом N2 — 0/30 гонок |
| N5 | GREEN | tenant-isolation на живой БД | до: FAIL; после: 2/2 прогона exit=0 (см. ниже) |
Предполагаемые контракты для OWN-01..11 (C1 роли точки/бухгалтера/директора, C2 пакет Сверх таблицы TASK-GROK-2 §2 (по тексту AUDIT.md): K1/SYNC-I (анонимный доступ без токенов на
зарплаты и согласование, C3 согласие на биометрию) описаны в `NOTES-FOR-EXECUTORS.md` §1. `/sync`), K1/PILOT-1 (подстановки без `:?` названы в K1), K2/PILOT-2 (development в пилоте — суть K2).
### Механизм для красных сценариев (храповик) ## Приёмка веток исполнителей (TASK-GROK-2 §4)
Выбран список `tests/e2e/known-defects.json` и одна задача `e2e`, а не отдельная Способ: `git worktree add --detach <dir> origin/<ветка>`, поверх — `tests/e2e/` и `scripts/test/`
задача «allowed to fail». Причины: задача с `continue-on-error` зеленеет при любом из этой ветки (в ветки исполнителей ничего не коммитилось), затем
сбое, включая сломанный стенд, а храповик различает три случая: (1) стенд сломан `E2E_MIGRATIONS=roundtrip E2E_DATABASE_NAME=krona_staff_acc node scripts/test/run-e2e.mjs`.
(`[HARNESS]`/`[CTRL]` красные или тест без ID) → exit 1; (2) новый красный ID не из «Было» — основная ветка `f5ac179` с тем же стендом (45 red / 9 green, храповик OK). Полные таблицы —
списка (регрессия) → exit 1; (3) ID из списка стал зелёным → exit 1 с просьбой `tests/e2e/report-evidence/acceptance-*.txt`. Ни одна ветка не влита в основную
вычеркнуть его (исправление фиксируется, откат снова станет красным). Строгий режим (`git merge-base --is-ancestor` → нет), поэтому `known-defects.json` не сокращён.
`--no-known-defects` показывает полную картину.
## N5: tenant-isolation до/после ### `codex/fix-live-database` @ ea68e12
До (база 778ba2d, `DATABASE_TEST_URL` = роль приложения), [`evidence/n5-before.txt`](evidence/n5-before.txt):
```text ```text
FAIL tests/integration/tenant-isolation.test.ts > PostgreSQL tenant isolation == roundtrip OK: rolledBack=11 reapplied=11
error: permission denied for table staff_employees Tests 41 failed | 13 passed (54)
❯ tests/integration/tenant-isolation.test.ts:40:7 | K13/SYNC-A | GREEN | 1/1 | зелёный — ИСПРАВЛЕН, уберите из known-defects.json |
Test Files 1 failed (1) | K13/SYNC-B | GREEN | 1/1 | зелёный — ИСПРАВЛЕН, уберите из known-defects.json |
Tests 1 passed (1) | K3/N2 | GREEN | 1/1 | зелёный — ИСПРАВЛЕН, уберите из known-defects.json |
exit=1 | K4/N1 | GREEN | 1/1 | зелёный — ИСПРАВЛЕН, уберите из known-defects.json |
``` ```
После (очистка соединением мигратора `DATABASE_TEST_MIGRATION_URL`, с | ID | Было | Стало | Доказательство |
`app.tenant_id` из-за FORCE RLS, до и после теста), [`evidence/n5-after.txt`](evidence/n5-after.txt): | ---------- | ---- | ----- | ------------------------------------------------------------------------------------------------------------ |
| 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) |
Вердикт: K3 и K4 исправлены (подтверждено на живой PostgreSQL 17, миграции roundtrip). После влития
убрать `K3/N2`, `K4/N1` из `known-defects.json`. Цепочка расчёта теперь доходит до денежных дефектов:
K5 и K6 подтверждены по существу.
### `codex/fix-format-baseline` @ fd1ec3d
```text ```text
== run 1 ... Test Files 1 passed (1) Tests 1 passed (1) exit=0 Tests 43 failed | 11 passed (54)
== run 2 ... Test Files 1 passed (1) Tests 1 passed (1) exit=0 | 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!
``` ```
Полный `npm run check` с тестовой БД (задача verify в `ci-local.sh`): | ID | Было | Стало | Доказательство |
`Test Files 19 passed (19)`, `Tests 188 passed (188)`. | ---------- | -------------- | ----- | ------------------------------------------------- |
| 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
```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 |
| остальные | — | — | других отличий нет |
Вердикт: K13 исправлен. Ветка основана на `778ba2d`, не на `331e490`. Убрать `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`) ## CI (`.forgejo/workflows/ci.yml`)
- `verify`: сервис `postgres:17-alpine@sha256:18cfe3ef…` (bootstrap-пользователь только - `verify`: `postgres:17-alpine@sha256:…`, роли как в pilot (мигратор NOSUPERUSER, app NOBYPASSRLS),
для provision), роли как в pilot (мигратор без SUPERUSER, app без BYPASSRLS), миграции `up → откат всех 11 → up → up`, `npm run check` с тестовой БД, `npm audit`.
миграции `up → откат всех 11 → up → up (no-op)`, `npm run check` с - `format`: отдельно (F14 красный до влития `codex/fix-format-baseline`).
`DATABASE_TEST_URL`/`DATABASE_TEST_MIGRATION_URL` (tenant-isolation реально - `e2e`: PostgreSQL 17, `E2E_MIGRATIONS=roundtrip`, храповик; evidence — артефакт `e2e-evidence`
выполняется), `npm audit --audit-level=high`. (`actions/upload-artifact@ea165f8… # v4.6.2`, Forgejo 9.0.3).
- `format`: отдельно, чтобы F14 (23 файла вне моих путей) не маскировал остальное. - `windows`: выключена без раннера (см. выше). `docker-build`: метка `docker-host`, `needs: verify`, только push.
- `e2e`: тот же сервис PostgreSQL 17, `E2E_MIGRATIONS=roundtrip`, храповик. - Все actions закреплены SHA (F13), SHA совпадают на github.com и code.forgejo.org.
- `docker-build`: отдельная задача на метке `docker-host`, `needs: verify`, только push.
- Все actions закреплены SHA (F13): `actions/checkout@11d5960a… # v4.4.0`,
`actions/setup-node@49933ea5… # v4.4.0` (SHA получены `git ls-remote`, совпадают на
github.com и code.forgejo.org).
Валидация: `actionlint -config-file actionlint.yaml .forgejo/workflows/ci.yml` (1.7.12, Механизм красных сценариев — храповик `known-defects.json`, а не «allowed to fail»: сломанный стенд,
метки `docker`, `docker-host`) → 0 замечаний, exit 0. Локальный эквивалент новый красный ID или исправленный, но не вычеркнутый ID делают задачу `e2e` красной.
`./scripts/test/ci-local.sh` ([`evidence/ci-local.txt`](evidence/ci-local.txt)):
Валидация: `actionlint -config-file actionlint.yaml .forgejo/workflows/ci.yml` (1.7.12, метки
`docker`, `docker-host`, `windows`) → 0 замечаний. Локально `./scripts/test/ci-local.sh`:
```text ```text
::: [migrate-roundtrip] OK (== roundtrip OK: rolledBack=11 reapplied=11) ::: [migrate-roundtrip] OK (== roundtrip OK: rolledBack=11 reapplied=11)
::: [check] OK (Test Files 19 passed, Tests 188 passed) ::: [check] OK (Test Files 20 passed (20), Tests 196 passed (196))
::: [audit] OK (found 0 vulnerabilities) ::: [audit] OK (found 0 vulnerabilities)
::: [format-check] FAIL exit=1 (Code style issues found in 23 files — F14) ::: [format-check] FAIL (Code style issues found in 23 files — F14)
::: [e2e] OK (храповик: все красные — известные дефекты) ::: [e2e] OK (Tests 45 failed | 9 passed (54), храповик OK)
=== итог: status=1 (только из-за format)
``` ```
## Проверка pilot compose (PILOT-1..3) ## N13 (гонка первого расчёта)
`tests/e2e/40-compose-pilot.e2e.ts` читает `deploy/pilot/compose.yaml` (только чтение). `node scripts/test/n13-race.mjs` (10/25/50 параллельных первых расчётов × 5 попыток) —
Положен в `tests/e2e/`, а не рядом с существующим тестом корневого compose [`report-evidence/n13-race.txt`](report-evidence/n13-race.txt): как есть 15/15 → 500 (K3); с обходом K3
(`tests/unit/deployment.test.ts`), потому что `tests/unit/` вне разрешённых путей. в одноразовой БД — 0 некорректных из 30 (с подтверждённой сменой и без). Не воспроизведено, как и у
Результат: PILOT-1 RED (14 подстановок без `:?`), PILOT-2 RED (`NODE_ENV: development` в владельца; вероятная причина (гипотеза по коду) — upsert `ensureDefaultPayRules` сериализует расчёты
`app`; конфликт с запретом standalone в production в `config.ts` описан в заметках), тенанта. Прогон до перебазирования, на `778ba2d`.
PILOT-3 GREEN.
## N13: гонка первого расчёта
`node scripts/test/n13-race.mjs` — 10/25/50 параллельных первых `POST /payroll/calculate`,
5 попыток на размер, каждая попытка на новой точке. Вывод: [`evidence/n13-race.txt`](evidence/n13-race.txt).
```text
[n13] ИТОГ mode=as-is sessions=false: попыток 15, некорректных 15 (все 500: N2, occurred_at)
[n13] ИТОГ mode=n2-bypass sessions=false: попыток 15, некорректных 0 (200×N, 1 run_id, 1 расчёт)
[n13] ИТОГ mode=n2-bypass sessions=true: попыток 15, некорректных 0 (с подтверждённой сменой, db_lines=1)
```
`n2-bypass` — диагностика: в одноразовой БД скрипта мигратор ставит
`DEFAULT now()` для `staff_outbox.occurred_at`; продукт и миграции не меняются.
Вердикт: **гонка не воспроизведена (0 из 30 попыток при 10/25/50)**, совпадает с
«N13 not reproduced» владельца. Вероятная причина (из кода, отдельно не проверено):
upsert `ensureDefaultPayRules` в начале расчёта случайно сериализует расчёты одного
тенанта. Окно в коде остаётся — рекомендация в заметках §5.
## Не проверено ## Не проверено
- Реальный Forgejo-раннер: workflow не запускался на сервере (нет API-токена, задачи - Реальный Forgejo-раннер (workflow выполнялся только локальным эквивалентом), `docker build`.
выполнены локально эквивалентным скриптом). Раннера с меткой `docker-host` может не быть. - Запуск на Windows 11 (раннера нет); Windows-only: DPAPI, ACL каталога данных, установщик, WinUI,
- `docker build` не запускался (на этой машине нет Docker). named pipe — см. тесты Codex в `apps/KronaStaffTerminal.Tests`.
- Windows-only: DPAPI, ACL каталога данных, установщик, WinUI, реальный named pipe — - OWN-01..11, K8 (C4) и K9 (C5) проверяют предполагаемые контракты из NOTES.
не проверено; см. тесты Codex в `apps/KronaStaffTerminal.Tests`. - K5/N4, K6/D5, K8/D1 на основной ветке блокированы K3; по существу проверены только на
- Сопоставление с ID владельца K1/K3/K4/K5/K6/K10/K11/K12, S1/S2/S9, A4 и сверка с его `codex/fix-live-database`.
`live-audit.mjs`/`live-sync.mjs` — файлов нет. - S9 проверен удалением SQL-запросом мигратора (публичного API удаления точки нет).
- N4 и SYNC-C по существу: блокированы N2 и SYNC-C0 соответственно.
- OWN-01..11 проверяют предполагаемый контракт; реальный контракт Codex может отличаться.
- Причина невоспроизводимости N13 — гипотеза по коду. - Причина невоспроизводимости N13 — гипотеза по коду.