krona-staff/tests/e2e/NOTES-FOR-EXECUTORS.md
grok 1e325a6dfd test(e2e): сценарии решений владельца (OWN-01..11) и заметки исполнителям
Зачем: решения владельца (роли точки/бухгалтера/директора, пакет зарплаты
с подписью директора, неизменяемость после подписи, зарплата по точке
фактической смены, согласие на биометрию) должны стать исполняемыми
критериями приёмки до того, как Codex начнёт этап 4, иначе реализацию
будет нечем проверить.

API ещё нет, поэтому тесты написаны против наиболее вероятного контракта
C1 (access-grants), C2 (payroll/batches, submit/sign/return,
notifications), C3 (biometric-consent, terminal enrollment); контракт и
инварианты описаны в tests/e2e/NOTES-FOR-EXECUTORS.md. Сейчас все OWN
красные (404 Route not found) и внесены в known-defects.json.
В NOTES также: найденные сценариями дефекты с указанием файлов,
конфликт NODE_ENV/standalone для pilot, N6, требования к раннеру docker-host.
2026-09-26 14:30:46 +07:00

15 KiB
Raw Blame History

Заметки для исполнителей (Codex / Antigravity)

Автор: Grok (стенд тестов и независимая приёмка). Я не меняю src/, apps/, migrations/, deploy/; всё, что требует изменения продукта, описано здесь.

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

# bootstrap-админ PostgreSQL 17 (только создаёт роли и БД; сервер и миграции под ним не работают)
export E2E_PG_ADMIN_URL=postgres://<admin>:<pass>@127.0.0.1:55433/postgres
node scripts/test/run-e2e.mjs                    # храповик known-defects (как в CI)
node scripts/test/run-e2e.mjs --no-known-defects # строгий режим: все дефекты красные
E2E_MIGRATIONS=roundtrip node scripts/test/run-e2e.mjs  # + откат всех миграций перед запуском
./scripts/test/ci-local.sh                       # локальный эквивалент verify/format/e2e

Правило храповика. Исправили дефект → его сценарий позеленел → run-e2e.mjs падает с «дефект исправлен, обновите known-defects.json» — удалите ID из tests/e2e/known-defects.json в том же коммите. Новый красный сценарий, которого нет в списке, — регрессия.

1. Предполагаемый контракт для решений владельца (OWN-01..11) — Codex, этап 4

API пока нет, поэтому сценарии tests/e2e/30-owner-decisions.e2e.ts написаны против наиболее вероятного контракта. Если выбираете другой — поменяйте тесты и этот раздел в том же PR, смысл проверок (кто что может и когда) менять нельзя. Все пути относительно /api/v1/staff.

C1. Роли точки/бухгалтера/директора (standalone-аналог Core RBAC)

  • POST /access-grants (только главный менеджер, staff:employee:write) тело { "role": "point_manager" | "accountant" | "director", "org_unit_ids": ["<ULID>"], "display_name": "..." } → 201 { "id", "role", "org_unit_ids", "access_token" }. Токен показывается один раз, хранится как хеш (как ключи сотрудников), используется как Authorization: Bearer.
  • point_manager видит/считает только свои org_unit_ids; чужая точка → 403 (и для нового POST /payroll/batches, и для старого POST /payroll/calculate).
  • В platform-режиме те же роли приходят из Core (User ≠ Employee, AGENTS.md).

C2. Пакет зарплаты точки и согласование

Метод и путь Кто Результат
POST /payroll/batches {org_unit_id, from, to} accountant; point_manager своей точки 201 {id, status:"draft", org_unit_id, lines:[{work_session_id, employee_id, org_unit_id, amount_minor}]}
POST /payroll/batches/:id/submit только accountant 200 {status:"submitted"}; point_manager → 403; повтор после подписи → 409
GET /notifications любой пользователь 200 {notifications:[{type:"payroll.batch.submitted", subject_id:<batchId>}]}; каждому директору ровно одно уведомление на отправку
POST /payroll/batches/:id/sign только director 200 {status:"signed"}; accountant → 403; если смена пакета изменилась после отправки → 409, пакет получает status:"returned"
POST /payroll/batches/:id/return {comment} director 200 {status:"returned", return_comment}
GET /payroll/batches/:id accountant, director 200 {status, return_comment, lines:[...]}

Инварианты, которые проверяют тесты:

  • до подписи в staff_payroll_ledger для сотрудников пакета нет ни accrual, ни payout (OWN-05);
  • подпись атомарно пишет accrual и payout; после подписи PATCH /work-sessions/:id для смен пакета → 409, пакет не отправляется повторно (OWN-06);
  • строка пакета принадлежит точке, где отработана смена (staff_work_sessions.org_unit_id), а не основной точке сотрудника (OWN-09).

C3. Согласие на биометрию

  • PUT /employees/:id/biometric-consent { "status": "granted" | "revoked", "document_ref": "..." } (главный менеджер) → 200.
  • GET /terminal/employees/:id/enrollment (терминальный токен) → 200 { "employee_id", "registration_allowed": boolean, "reason": "consent_missing" | "consent_revoked" | null }. Без записи о согласии — false/consent_missing (OWN-10); после отзыва — false/consent_revoked (OWN-11).
  • Шаблоны отпечатков на сервер не передаются (docs/TERMINAL-API-REQUIREMENTS.md); сервер хранит только факт согласия. Тот же флаг стоит отдать в /sync/pull (employee payload).

Сейчас все OWN красные с 404 Route ... not found (C1–C3 не реализованы); OWN-03..09 дополнительно упрутся в N2 (подтверждение смены падает 500).

2. Дефекты продукта, найденные сценариями (все красные сейчас)

Команда и фактический вывод — tests/e2e/evidence/e2e-run.txt; таблица — tests/e2e/REPORT.md.

ID Файл Что исправить
N1 src/portal.ts POST /employees 42P08 inconsistent types deduced for parameter $2: явные $2::varchar(26), $5::text или отдельный запрос профессии
N2 src/portal.ts PATCH /work-sessions, src/domain/payroll/service.ts calculate/approve staff_outbox.occurred_at NULL (23502): передавать occurred_at или DEFAULT now() новой миграцией
N4 service.ts после N2 проверить: пересекающийся период не должен начислять смену повторно (409 или исключение уже начисленных смен)
N3 push-подписки allowlist push-сервисов / запрет приватных адресов, redirect:'manual'; сейчас сервер подключается к 127.0.0.1
N9 push-dispatcher.ts ON CONFLICT ... DO UPDATE SET employee_id → 409 для чужого endpoint
N10 GET /professions, /org-units требовать право справочника; терминал сейчас получает 200
N11 импорт валидировать kind и схему items → 400 вместо 500 (23514)
N12 PUT pay-assignment additionalProperties:false, верхние границы сумм
F2 config.ts/identity.ts standalone без токенов не должен выдавать права (процесс без токенов отдаёт 200 на /portal, /payroll, POST /professions)
F3 POST/DELETE /session в cookie — идентификатор сессии, а не сырой токен; logout отзывает сессию
F4 вход rate limit (тест ждёт 429 не позже 30 неверных попыток; идёт на отдельном процессе, чтобы лимит не мешал другим сценариям)
F7 POST /attendance/events использовать Idempotency-Key / staff_idempotency_keys
F10 assertSameOrigin мутации по cookie без Origin отклонять
SYNC-A/B src/domain/sync/service.ts устройство не должно выставлять confirmed/approved (сейчас applied)
SYNC-C0 pull() bootstrap SELECT ... status FROM staff_org_units — такого столбца нет → 500 на любой начальной загрузке
SYNC-C pull() bootstrap при has_more=true отдаётся next_cursor=latest, остаток после 100 сущностей теряется; нужен курсор снимка
SYNC-D портал/импорт изменения вне sync не пишутся в staff_sync_change_log → терминал не узнаёт о новых/изменённых сотрудниках
SYNC-E executeCommand проверять status='active' сотрудника
SYNC-F executeCommand сохранять org_unit_id в staff_attendance_events; payload не может переопределить точку устройства
SYNC-G push() ошибки БД одной команды → rejected для неё, а не 500 и откат всего пакета
SYNC-H /sync/devices assertCookieMutationOrigin как в портале
SYNC-I requireManagerContext без токенов не выдавать права (как F2)
SYNC-J executeCommand окно времени отметки как в POST /attendance/events (−180 дней … +5 мин)
SYNC-K acknowledge() отклонять курсор больше MAX(cursor_seq)

3. deploy/ (PILOT-1, PILOT-2) — Codex

  • PILOT-1: все подстановки в deploy/pilot/compose.yaml → ${VAR:?VAR is required} (сейчас 14 без :?: пустой STANDALONE_MANAGER_TOKEN молча превращает pilot в F2).
  • PILOT-2: NODE_ENV: development в app. Конфликт: config.ts запрещает STAFF_RUNTIME_MODE=standalone при NODE_ENV=production. Нужно решение: либо разрешить standalone в production при обязательных токенах и HTTPS, либо отдельный режим для pilot. Тест требует только «не development».
  • N6: scripts/create-app-role.mjs не работает мигратором без SUPERUSER («Only roles with SUPERUSER may change the SUPERUSER attribute»). CI и e2e обходятся scripts/test/provision-pg.mjs (роли создаёт bootstrap-админ, как docker/postgres/init-app-role.sh). В pilot мигратор = POSTGRES_USER = суперпользователь; рекомендую схему как в provision-pg.mjs.

4. CI

  • Задаче docker-build нужен раннер с меткой docker-host (Docker daemon). Без него задача ждёт в очереди; остальные задачи не блокируются.
  • format красная, пока не исправлен F14 (25 файлов, в основном src/, docs/, tests/unit/).
  • Реальный Forgejo-раннер мной не запускался (нет API-токена) — не проверено.