# Руководство по развёртыванию и чек-лист боевого тестирования Krona Staff ## 1. Назначение и архитектурные инварианты Krona Staff — автономно развёртываемый блок Krona Business Platform, отвечающий за учёт рабочего времени, терминалы посещаемости и управленческий расчёт зарплаты. ### Ключевые инварианты (AGENTS.md) 1. **Разделение доменов**: Core владеет Tenant, User, Role, SSO, Platform Settings, Files, Notifications. Staff владеет Employee и HR-доменом (`User != Employee`). 2. **Изоляция арендаторов**: Все таблицы содержат `tenant_id` (ULID 26 символов). В PostgreSQL включен и принудительно активирован Row-Level Security (`FORCE ROW LEVEL SECURITY`). Запросы выполняются через tenant-scoped транзакции с выставлением `app.tenant_id`. 3. **Разделение ролей БД**: - `krona_migrator` — владелец схемы и таблиц, выполняет миграции DDL, `NOSUPERUSER NOBYPASSRLS`. - `krona_staff_app` — runtime-пользователь приложения с минимальными DML правами (SELECT, INSERT, UPDATE, DELETE на прикладные таблицы; нет прав на `schema_migrations`, нет прав DDL). 4. **Типы данных**: - Идентификаторы — ULID (`VARCHAR(26)`). - Денежные суммы — `BIGINT` в минимальных единицах (копейках/minor units) + ISO 4217 код валюты (например, `'RUB'`). - Временные метки — UTC (`TIMESTAMPTZ`). 5. **Надёжность**: Транзакционный Outbox, идемпотентность по `Idempotency-Key`, CSRF-защита сессионных cookie (`Origin`/`Sec-Fetch-Site`), Rate-limiting ключей входа. --- ## 2. Развёртывание и запуск ### 2.1. Системные требования - **ОС**: Linux (Ubuntu 22.04+/Debian 12+) или Windows Server 2022+ / Windows 11 (для сборки терминала). - **Среда выполнения**: Node.js 22 LTS, npm 10+. - **База данных**: PostgreSQL 17 (поддерживается 17.0+). - **Сеть**: Порт 8080 (HTTP) или 8453 (HTTPS пилот). ### 2.2. Запуск через Docker Compose (рекомендуемый пилот) ```bash # 1. Перейдите в каталог deploy/pilot cd deploy/pilot # 2. Задайте ULID организации из Krona Business Platform export STANDALONE_TENANT_ID="01J0000000000000000000000A" # 3. Выполните скрипт первичной инициализации: # - Сгенерирует стойкие пароли БД, токены менеджера и терминала в secrets/ # - Сгенерирует ключи Web Push (VAPID) # - Создаст самоподписанный TLS-сертификат для терминала ./setup.sh # 4. Запустите стек (PostgreSQL + мигратор + app) docker compose up -d --build # 5. Проверьте запуск контейнеров и миграций docker compose ps docker compose logs -f app ``` ### 2.3. Запуск без Docker (Bare-Metal / Dev стенд) 1. **Создайте базу данных и роли в PostgreSQL**: ```sql -- Под пользователем postgres (DBA): CREATE ROLE krona_migrator LOGIN PASSWORD 'migrator_secure_password' NOSUPERUSER NOCREATEDB NOCREATEROLE NOBYPASSRLS; CREATE ROLE krona_staff_app LOGIN PASSWORD 'app_secure_password' NOSUPERUSER NOCREATEDB NOCREATEROLE NOINHERIT NOBYPASSRLS; CREATE DATABASE krona_staff OWNER krona_migrator ENCODING 'UTF8'; \c krona_staff ALTER SCHEMA public OWNER TO krona_migrator; REVOKE CREATE ON SCHEMA public FROM PUBLIC; REVOKE ALL ON DATABASE krona_staff FROM PUBLIC; GRANT CONNECT ON DATABASE krona_staff TO krona_staff_app; ``` 2. **Настройте переменные окружения (`.env`)**: ```bash cp .env.example .env ``` Укажите в `.env`: ```ini PORT=8080 NODE_ENV=production STANDALONE_TENANT_ID=01J0000000000000000000000A STANDALONE_ACTOR_ID=01J00000000000000000000001 STANDALONE_MANAGER_TOKEN=your_secure_manager_token_hex_or_ulid STANDALONE_TERMINAL_TOKEN=your_secure_terminal_token_hex_or_ulid DATABASE_URL=postgres://krona_staff_app:app_secure_password@127.0.0.1:5432/krona_staff DATABASE_MIGRATION_URL=postgres://krona_migrator:migrator_secure_password@127.0.0.1:5432/krona_staff ``` 3. **Сгенерируйте VAPID ключи**: ```bash node scripts/generate-vapid-keys.mjs >> .env ``` 4. **Примените миграции**: ```bash npm run db:migrate ``` 5. **Соберите и запустите приложение**: ```bash npm run build npm run start ``` --- ## 3. Чек-лист ручного боевого тестирования владельцем Каждый шаг должен быть выполнен последовательно. Ожидаемый результат сверяется с фактическим. | № | Шаг / Сценарий | Действие / HTTP-запрос | Ожидаемый результат | Статус | | ------ | ------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | :----: | | **1** | **Системные проверки (Health)** | `GET /api/v1/staff/live`
`GET /api/v1/staff/ready`
`GET /api/v1/staff/version` | HTTP 200 на всех эндпоинтах. `ready` подтверждает соединение с БД под ролью `krona_staff_app`. `version` возвращает `runtime: standalone` и semver. | [ ] | | **2** | **Контракт OpenAPI** | `GET /api/v1/staff/openapi.json` | HTTP 200, возвращается валидная спецификация OpenAPI 3.1 со всеми роутами модуля. | [ ] | | **3** | **Защита доступа (401)** | `GET /api/v1/staff/portal` без заголовка Authorization | HTTP 401 Unauthorized. Ошибки с деталями SQL отсутствуют. | [ ] | | **4** | **Защита от CSRF** | `POST /api/v1/staff/portal/employees` с cookie сессии, но без заголовка `Origin` или с чужим Origin | HTTP 403 Forbidden (CSRF token missing/invalid). | [ ] | | **5** | **Защита от перебора паролей (429)** | 35 последовательных запросов с неверным токеном/ключом | После исчерпания лимита возвращается HTTP 429 Too Many Requests с заголовком `Retry-After`. | [ ] | | **6** | **Создание сотрудника** | `POST /api/v1/staff/employees`
Headers: `Authorization: Bearer `
Body: `{"personnel_number": "EMP-001", "display_name": "Иванов Иван", "profession_code": "cook"}` | HTTP 201 Created. Создан сотрудник, присвоен ULID, запись зафиксирована в `staff_employees`. | [ ] | | **7** | **Проверка справочника профессий** | `GET /api/v1/staff/professions` | HTTP 200, список активных профессий (`cook`, `cashier`, `barista`, `waiter` и др.). | [ ] | | **8** | **Назначение тарифной ставки сотруднику** | `POST /api/v1/staff/payroll/assignments`
Body: привязка правила (почасовая ставка 350 руб/час = 35000 minor units). | HTTP 201 Created. Версионированное правило сохранено в `staff_pay_assignments`. | [ ] | | **9** | **Отметка прихода через терминал** | `POST /api/v1/staff/attendance/events`
Headers: `Authorization: Bearer `
Body: `{"event_id": "", "employee_id": "", "event_type": "check_in", "occurred_at": ""}` | HTTP 201 Created. Событие записано в `staff_attendance_events`. Отправлено событие в outbox. | [ ] | | **10** | **Идемпотентность отметки** | Повтор запроса шага 9 с тем же `event_id` и `source_event_id` | HTTP 200 OK (или 201), дубликат не создаётся, количество записей не удваивается. | [ ] | | **11** | **Отметка ухода через терминал** | `POST /api/v1/staff/attendance/events` с `event_type: "check_out"` спустя 8 часов | HTTP 201 Created. Формируется завершённая рабочая смена. | [ ] | | **12** | **Подтверждение смены менеджером** | `POST /api/v1/staff/work-sessions/:id/confirm` | HTTP 200 OK. Смена переходит в статус `confirmed`, часы готовы к начислению. | [ ] | | **13** | **Авторасчёт ведомости зарплаты** | `POST /api/v1/staff/payroll/runs/auto-calc`
Body: период с 1 по 15 число месяца. | HTTP 201 Created. Начислено: 8 ч × 350 руб = 2800 руб (280000 minor units). Сохранена расшифровка формулы расчёта. | [ ] | | **14** | **Согласование ведомости бухгалтером** | `POST /api/v1/staff/payroll/batches/:id/approve` | HTTP 200 OK. Статус пакета переходит в `approved`. | [ ] | | **15** | **Подписание ведомости директором** | `POST /api/v1/staff/payroll/batches/:id/sign`
Body: `{"signature": "director_sign_key", "note": "Выплата за первую половину месяца"}` | HTTP 200 OK. Пакет подписан. Созданы строки начислений в `staff_payroll_ledger` с полем `note` (без ошибки 500!). | [ ] | | **16** | **Проверка реестра выплат (Ledger)** | `GET /api/v1/staff/payroll/ledger` | HTTP 200 OK. Отображаются начисления и выплаты, сальдо корректно сходится. | [ ] | | **17** | **Web Push уведомления сотруднику** | `POST /api/v1/staff/portal/push-subscriptions` с валидным FCM/W3C endpoint. | HTTP 201 Created. Подписка сохранена. При наступлении события формируется delivery без ложных блокировок хоста. | [ ] | | **18** | **Уведомления директору без дублей** | `GET /api/v1/staff/notifications/unread` под учётной записью директора | HTTP 200 OK. Каждому директору отдаются ровно его уведомления без дублирования на каждого получателя. | [ ] | | **19** | **Офлайн-синхронизация: регистрация терминала** | `POST /api/v1/staff/sync/devices/register` с токеном менеджера | HTTP 201 Created. Возвращается токен устройства `kstaff_dev_...`. Токен сохраняется в терминале. | [ ] | | **20** | **Офлайн-синхронизация: начальный Pull (C0)** | `GET /api/v1/staff/sync/pull` с токеном устройства (без курсора) | HTTP 200 OK. Возвращаются справочники (сотрудники, оргструктура, тарифы) и курсор `cursor_ulid`. | [ ] | | **21** | **Офлайн-синхронизация: пакет команд Push** | `POST /api/v1/staff/sync/push` с массивом офлайн-отметок посещаемости | HTTP 200 OK. Все корректные команды применены, битые команды получают понятную ошибку, статус транзакции не ломает весь пакет. | [ ] | | **22** | **Отзыв прав уволенного сотрудника** | Перевод сотрудника в статус `fired` / деактивация доступа в `staff_access_grants` | Попытка входа уволенного сотрудника немедленно возвращает HTTP 401 Unauthorized. | [ ] | | **23** | **Проверка утилиты миграции арендатора (Rehome)** | `node scripts/rehome-tenant.mjs --dry-run --source --target ` | Утилита производит корректное сравнение количества строк по всем таблицам (`Number`) без сбоев типов и ошибок внешних ключей. | [ ] | --- ## 4. Диагностика и устранение неполадок 1. **Ошибка `401 Unauthorized` при запросе от терминала**: - Проверьте, передан ли заголовок `Authorization: Bearer ` или токен зарегистрированного устройства. 2. **Ошибка `403 Forbidden` при запросах из веб-интерфейса**: - Убедитесь, что запрос отправляется с доверенного Origin (`localhost`, `127.0.0.1` или IP пилота). Сессионные мутирующие запросы требуют валидный Origin. 3. **Проверка логов транзакций и RLS**: ```bash # Просмотр логов контейнера docker compose logs -f app ``` 4. **Проверка состояния миграций БД**: ```bash node scripts/check-migrations.mjs ```