krona-staff/tests/e2e/REPORT.md
grok 537b784da3 docs(e2e): REPORT.md (описание PR) и свежие evidence прогона
Зачем: PR в Forgejo создать нельзя (нет API-токена), поэтому описание
изменений, таблица green/red по ID с командами и фактическим выводом, итог
N5 до/после, валидация CI и N13 лежат в репозитории рядом с тестами.
Evidence перезаписаны финальным прогоном ./scripts/test/ci-local.sh, чтобы
отчёт ссылался на те же числа, что в файлах (38 red / 9 green, храповик OK,
format красный из-за F14). Число файлов F14 в комментарии ci.yml и в
заметках уточнено до 23 по этому замеру.
2026-09-26 15:18:31 +07:00

22 KiB
Raw Blame History

PR: e2e-стенд на реальной PostgreSQL 17 и независимая приёмка (grok/e2e-harness)

База: codex/bootstrap-platform-foundation @ 778ba2d. Сравнение: 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/**. src/, apps/, migrations/, deploy/, package.json не тронуты. Всё, что требует правки продукта, — в NOTES-FOR-EXECUTORS.md.

Зачем

156 зелёных тестов на поддельной БД не заметили, что создание сотрудника, подтверждение смены и расчёт зарплаты на реальной PostgreSQL падают всегда. Этот PR добавляет стенд, где реальный сервер (tsx src/server.ts) работает с реальной PostgreSQL 17 под ролью приложения без привилегий, миграции применяет мигратор без SUPERUSER, а все токены и пароли случайные (crypto.randomBytes). Каждый сценарий проверяет правильное поведение, поэтому красный цвет означает открытый дефект.

Источник ID

Файлы владельца (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 владельца K1, K3, K4, K5, K6, K10, K11, K12, S1, S2, S9, A4 не сопоставлены — не проверено. Синхронизация помечена SYNC-* (мои ID, вероятно пересекаются с S1–S9 владельца). Когда файлы владельца будут доступны, достаточно переименовать теги в названиях тестов (например [K3/N1]) и ключи в known-defects.json.

Коммиты

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

Как запустить

# PostgreSQL 17, bootstrap-админ нужен только для создания ролей и БД
export E2E_PG_ADMIN_URL=postgres://<admin>:<pw>@127.0.0.1:55433/postgres
node scripts/test/run-e2e.mjs                 # храповик known-defects (как в CI)
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                    # локальный эквивалент всех задач CI

Роли (provision-pg.mjs): krona_migrator — NOSUPERUSER NOCREATEDB NOCREATEROLE, владелец БД и схемы public; krona_staff_app — NOSUPERUSER NOBYPASSRLS, права выдают миграции 0003/0004. Пароли случайные, пишутся в env-файл 0600 во временном каталоге, который удаляется после запуска.

Таблица green/red по ID

Команда: 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/ci-local.txt, JSON — evidence/e2e-results.json, логи серверов — evidence/server-*.txt.

 Test Files  4 failed | 1 passed (5)
      Tests  38 failed | 9 passed (47)
[run-e2e] OK: стенд работает, все красные сценарии — известные открытые дефекты
ID Цвет Что проверяется (правильное поведение) Фактически (из вывода)
HARNESS (×5) GREEN live/ready, PG17, app-роль без SUPERUSER/BYPASSRLS, токены, терминал 5/5 passed
CTRL (×2) GREEN RBAC терминала/сотрудника, уволенный теряет доступ 2/2 passed
N1 RED POST /employees → 201 500 42P08 inconsistent types deduced for parameter $2
N2 RED подтверждение смены, расчёт, утверждение → 200 500 23502 null value in column "occurred_at" of "staff_outbox"
N4 RED пересекающиеся расчёты не начисляют смену дважды блокирован N2 (500 23502 на предусловии) — сам N4 не проверен
N3 RED push на внутренний адрес отклоняется, соединений нет 201 и 1 входящее соединение на 127.0.0.1
N9 RED чужой push-endpoint не перепривязывается 201
N10 RED терминал не читает справочники (403) professions 200, org-units 200
N11 RED импорт с неверным kind → 400 500 23514 check constraint staff_payroll_ledger
N12 RED лишние поля/нереальные суммы → 400 200
F2 RED процесс без токенов не даёт прав анониму (401) /portal 200, /payroll 200, POST /professions 201
F3 RED cookie без сырого токена, logout отзывает rawTokenInCookie: true
F4 RED 429 не позже 30 неверных входов все 401, 429 нет
F7 RED повтор Idempotency-Key с другим телом не создаёт событие второе событие создано (201)
F10 RED мутация по cookie без Origin → 403 201
SYNC-A RED устройство не подтверждает смену applied
SYNC-B RED устройство не утверждает табель applied
SYNC-C0 RED bootstrap pull без курсора → 200 500 42703 column "status" does not exist (новая находка)
SYNC-C RED bootstrap >100 сущностей постранично без потерь 500 42703 (блокирован SYNC-C0)
SYNC-D RED изменения из портала видны в инкрементальном pull изменения нет
SYNC-E RED отметка уволенного через sync отклоняется applied
SYNC-F RED точка устройства сохраняется, чужая точка в payload не принимается applied, точка не сохранена
SYNC-G RED битая команда отклоняется по отдельности 500 22007 на весь пакет
SYNC-H RED регистрация устройства по cookie с чужим Origin → 403 201
SYNC-I RED без токенов анонимно устройство не регистрируется 201
SYNC-J RED отметка 2001 года через sync отклоняется applied
SYNC-K RED ack курсора из будущего → 400 200
SYNC-L GREEN отозванное устройство не синхронизируется (403) 403 (позитивный контроль)
OWN-01 RED менеджер точки A считает зарплату точки B → 403 404 Route POST:/api/v1/staff/access-grants not found (нет API)
OWN-02 RED менеджер отправляет пакет на согласование → 403 404 access-grants (нет API)
OWN-03 RED бухгалтер отправляет пакет → успех, каждому директору 1 уведомление 404 access-grants (нет API)
OWN-04 RED бухгалтер подписывает пакет → 403 404 access-grants (нет API)
OWN-05 RED до подписи в ledger нет начислений/выплат 404 access-grants (нет API)
OWN-06 RED подпись директора → ledger, пакет и смены неизменяемы 404 access-grants (нет API)
OWN-07 RED смена изменена после отправки → 409, пакет у бухгалтера 404 access-grants (нет API)
OWN-08 RED возврат с комментарием → пакет у бухгалтера, комментарий сохранён 404 access-grants (нет API)
OWN-09 RED смена сотрудника A в точке B → в расчёт точки B 404 access-grants (нет API)
OWN-10 RED без согласия на биометрию → терминалу «регистрация запрещена» 404 GET .../terminal/employees/:id/enrollment (нет API)
OWN-11 RED согласие отозвано → статус для терминала меняется 404 PUT .../biometric-consent (нет API)
PILOT-1 RED все подстановки в pilot compose — ${VAR:?} 14 переменных без :? (первая — STANDALONE_MANAGER_TOKEN)
PILOT-2 RED нет NODE_ENV=development сервис app
PILOT-3 GREEN у app нет кредов мигратора passed
N13 — гонка первого расчёта (скрипт, не vitest) см. ниже: как есть 15/15 → 500 (N2); с обходом N2 — 0/30 гонок
N5 GREEN tenant-isolation на живой БД до: FAIL; после: 2/2 прогона exit=0 (см. ниже)

Предполагаемые контракты для OWN-01..11 (C1 роли точки/бухгалтера/директора, C2 пакет зарплаты и согласование, C3 согласие на биометрию) описаны в NOTES-FOR-EXECUTORS.md §1.

Механизм для красных сценариев (храповик)

Выбран список tests/e2e/known-defects.json и одна задача e2e, а не отдельная задача «allowed to fail». Причины: задача с continue-on-error зеленеет при любом сбое, включая сломанный стенд, а храповик различает три случая: (1) стенд сломан ([HARNESS]/[CTRL] красные или тест без ID) → exit 1; (2) новый красный ID не из списка (регрессия) → exit 1; (3) ID из списка стал зелёным → exit 1 с просьбой вычеркнуть его (исправление фиксируется, откат снова станет красным). Строгий режим --no-known-defects показывает полную картину.

N5: tenant-isolation до/после

До (база 778ba2d, DATABASE_TEST_URL = роль приложения), evidence/n5-before.txt:

 FAIL  tests/integration/tenant-isolation.test.ts > PostgreSQL tenant isolation
error: permission denied for table staff_employees
 ❯ tests/integration/tenant-isolation.test.ts:40:7
 Test Files  1 failed (1)
      Tests  1 passed (1)
exit=1

После (очистка соединением мигратора DATABASE_TEST_MIGRATION_URL, с app.tenant_id из-за FORCE RLS, до и после теста), evidence/n5-after.txt:

== run 1 ... Test Files  1 passed (1)  Tests  1 passed (1)  exit=0
== run 2 ... Test Files  1 passed (1)  Tests  1 passed (1)  exit=0

Полный npm run check с тестовой БД (задача verify в ci-local.sh): Test Files 19 passed (19), Tests 188 passed (188).

CI (.forgejo/workflows/ci.yml)

  • verify: сервис postgres:17-alpine@sha256:18cfe3ef… (bootstrap-пользователь только для provision), роли как в pilot (мигратор без SUPERUSER, app без BYPASSRLS), миграции up → откат всех 11 → up → up (no-op), npm run check с DATABASE_TEST_URL/DATABASE_TEST_MIGRATION_URL (tenant-isolation реально выполняется), npm audit --audit-level=high.
  • format: отдельно, чтобы F14 (23 файла вне моих путей) не маскировал остальное.
  • e2e: тот же сервис PostgreSQL 17, E2E_MIGRATIONS=roundtrip, храповик.
  • 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, метки docker, docker-host) → 0 замечаний, exit 0. Локальный эквивалент ./scripts/test/ci-local.sh (evidence/ci-local.txt):

::: [migrate-roundtrip] OK          (== roundtrip OK: rolledBack=11 reapplied=11)
::: [check] OK                      (Test Files 19 passed, Tests 188 passed)
::: [audit] OK                      (found 0 vulnerabilities)
::: [format-check] FAIL exit=1      (Code style issues found in 23 files — F14)
::: [e2e] OK                        (храповик: все красные — известные дефекты)
=== итог: status=1                  (только из-за format)

Проверка pilot compose (PILOT-1..3)

tests/e2e/40-compose-pilot.e2e.ts читает deploy/pilot/compose.yaml (только чтение). Положен в tests/e2e/, а не рядом с существующим тестом корневого compose (tests/unit/deployment.test.ts), потому что tests/unit/ вне разрешённых путей. Результат: PILOT-1 RED (14 подстановок без :?), PILOT-2 RED (NODE_ENV: development в app; конфликт с запретом standalone в production в config.ts описан в заметках), PILOT-3 GREEN.

N13: гонка первого расчёта

node scripts/test/n13-race.mjs — 10/25/50 параллельных первых POST /payroll/calculate, 5 попыток на размер, каждая попытка на новой точке. Вывод: evidence/n13-race.txt.

[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-токена, задачи выполнены локально эквивалентным скриптом). Раннера с меткой docker-host может не быть.
  • docker build не запускался (на этой машине нет Docker).
  • Windows-only: DPAPI, ACL каталога данных, установщик, WinUI, реальный named pipe — не проверено; см. тесты Codex в apps/KronaStaffTerminal.Tests.
  • Сопоставление с ID владельца K1/K3/K4/K5/K6/K10/K11/K12, S1/S2/S9, A4 и сверка с его live-audit.mjs/live-sync.mjs — файлов нет.
  • N4 и SYNC-C по существу: блокированы N2 и SYNC-C0 соответственно.
  • OWN-01..11 проверяют предполагаемый контракт; реальный контракт Codex может отличаться.
  • Причина невоспроизводимости N13 — гипотеза по коду.