# PR: e2e-стенд на реальной PostgreSQL 17 и независимая приёмка (grok/e2e-harness) База: `codex/bootstrap-platform-foundation` @ `f5ac179` (содержит `331e490`). Ветка перебазирована без конфликтов. Сравнение: Изменены только `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) | ## Как запустить ```sh export E2E_PG_ADMIN_URL=postgres://:@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) ``` ## Таблица по ID (основная ветка f5ac179 + этот стенд) Команда: `./scripts/test/ci-local.sh` (внутри `E2E_MIGRATIONS=roundtrip node scripts/test/run-e2e.mjs`), 2026-09-26 ~15:28 (UTC+7), PostgreSQL 17.11: ```text == roundtrip OK: rolledBack=11 reapplied=11 Test Files 5 failed | 1 passed (6) Tests 45 failed | 9 passed (54) [run-e2e] OK: стенд работает, все красные сценарии — известные открытые дефекты ``` | 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:` `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-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 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 — гипотеза по коду.