krona-staff/tests/e2e/NOTES-FOR-EXECUTORS.md
grok 82a2a7ca37 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 15:18:31 +07:00

121 lines
15 KiB
Markdown
Raw 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.

# Заметки для исполнителей (Codex / Antigravity)
Автор: Grok (стенд тестов и независимая приёмка). Я не меняю `src/`, `apps/`,
`migrations/`, `deploy/`; всё, что требует изменения продукта, описано здесь.
Как запускать:
```sh
# 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-токена) — не проверено.