Почему: таблица приёмки и разбор C6 должны соответствовать текущим SHA
веток Codex, а владелец должен видеть, какие расхождения с C6 остались
после d52ecf3 и какая часть красного была ошибкой сценария (типы счётчиков,
d477dde), а не продукта.
- REPORT: таблица по повторному прогону (b022885, d52ecf3), разбор d52ecf3.
- NOTES C6: что закрыл d52ecf3; открыто — многострочный JSON, сумма журнала
в dry-run, dry-run по умолчанию и обязательные --apply/--actor/--backup-evidence.
- NOTES разд. 8: K18 на b022885 — ротация на ПК есть, на сервере нет,
личные пути в новых файлах.
Почему: пока шёл разбор, Codex запушил codex/fix-tenant-rehome d52ecf3
(17:02, синонимы C6) и codex/fix-pilot-backups b022885 (17:07, ротация на ПК).
Таблица по старым SHA устарела, поэтому все 7 веток прогнаны заново
(стенд a6a1b5e): round3-accept-f7d3c73-rerun.md.
В round3-k18-and-rehome.md, разд. 6: вывод [PILOT-REHOME] на d52ecf3 как есть
(многострочный JSON → «dry-run без JSON-отчёта»), пошаговая диагностика
в копии сценария, которую потом откатил (что упадёт дальше: сумма журнала
в dry-run, dry-run по умолчанию вместо переноса), и тесты backup на b022885.
Почему это ошибка сценария, а не подгонка под продукт: C6 не фиксирует тип
счётчиков, count(*) из PostgreSQL приходит в node-postgres строкой (bigint),
а сам сценарий уже фильтровал `Number(n) > 0` и сравнивал сумму журнала
через String(). Строгое сравнение '2' с 2 роняло проверку при верных числах
(диагностика на codex/fix-tenant-rehome d52ecf3: `expected { staff_attendance_events:
'2', …(26) } to deeply equal { staff_attendance_events: 2, …(26) }`).
Остальные проверки не ослаблены: JSON последней строкой, сумма журнала
в dry-run, перенос без флага и все сверки после переноса — как в C6.
На основе f7d3c73 [PILOT-REHOME] по-прежнему красный (скрипта нет),
run-e2e: `Tests 46 failed | 10 passed (56)`, `[run-e2e] OK`.
Зачем: Codex и владельцу нужен точный список отличий интерфейса
scripts/rehome-tenant.mjs (c73bb3b) от контракта C6, чтобы выбрать один
интерфейс, а не подгонять сценарий под продукт молча.
- C6: таблица отличий (флаги --source/--target, обязательный --apply
с --actor/--backup-evidence, DATABASE_REHOME_URL, нет --json,
многострочный JSON, нет суммы журнала в dry-run) и два варианта решения.
- Раздел 8: tenant-config (как закрыть — по ACCEPTANCE-3), rehome
(интеграционный тест в check/CI всегда skipped и требует суперпользователя),
K18 (нет globals, isolation.sql зашит на ULID, личный путь в доке,
ротации нет, тесты backup не в CI).
Зачем: владелец ждёт по ACCEPTANCE-3 таблицу приёмки всех открытых веток
на новой основе, включая три новые ветки Codex, и ответ, зеленеет ли
[PILOT-REHOME] на codex/fix-tenant-rehome.
- Таблица accept-branch по 7 веткам (стенд 18f944e); храповик не тронут,
потому что merge-base --is-ancestor показывает, что ничего не влито.
- fix-tenant-config: вывод владельца подтверждён (sync.test.ts:556).
- fix-tenant-rehome: [PILOT-REHOME] красный из-за интерфейса
(Unknown option --from); по существу перенос проверен на копии.
Сценарий не менялся: ошибок в нём нет.
- fix-pilot-backups (K18): тесты с моками зелёные, учебное восстановление
без Docker прошло, без globals в кластер без ролей не восстанавливается.
- «Не проверено»: то, что требует Docker/systemd/ssh и данных пилота.
Почему: backup.sh снимает только pg_dump -Fc одной БД, без ролей (globals). Проверено на
временном кластере PG17 без ролей krona_*: pg_restore exit=1, 61 ошибка «role krona_migrator does
not exist», данные есть, владелец — postgres. В пилоте роли, вероятно, создаются при инициализации
контейнера (POSTGRES_USER, docker/postgres/init-app-role.sh) — без Docker не проверено.
Почему: владелец просил прогнать все открытые ветки против новой основы f7d3c73, включая
codex/fix-tenant-config, codex/fix-tenant-rehome, codex/fix-pilot-backups, и проверить K18 и
scripts/rehome-tenant.mjs фактами.
round3-accept-f7d3c73.md — вывод accept-branch (7 веток, 28 пар merge-tree — конфликтов нет).
round3-k18-and-rehome.md — тесты Codex для backup; учебное восстановление без Docker (шим docker →
локальный pg_dump PG17, CMS-шифрование, sha256, pg_restore, restore-drill-report.sql совпал);
перенос заглушки на восстановленной копии интерфейсом продукта; [PILOT-REHOME] через
диагностический адаптер (не в продукте, не в сценарии) — исходники скриптов приложены.
Почему: TASK-GROK-3 — отчёт с командами и фактическим выводом и новые находки для исполнителей.
REPORT: раздел «Круг 3» (ожидания владельца на SHA из задания — все шесть совпали; проверка
форматирования; все открытые ветки после fetch 16:16 NOVT — конфликтов нет; локальная сборка
«после слияния» — храповик просит убрать ровно K3/N2, K4/N1, K13/SYNC-A, K13/SYNC-B, SYNC-C0;
PILOT-REHOME), новые числа ci-local (46 red / 10 green), «Не проверено».
NOTES: контракт C6 для scripts/rehome-tenant.mjs (с ограничениями схемы: RLS журнала, составные
FK, app.user_id в политиках INSERT) и раздел 7: двойное закрытие пула в sync-bootstrap.test.ts,
неописанный предохранитель live-postgres-remediation.test.ts, N5 в основе, FK outbox_id без tenant_id.
Почему: TASK-GROK-3 требует каждый вывод с командой и фактическим выводом.
round3-accept-owner-shas.md — 1df2019/8baad55/513cdff: все ожидания владельца подтвердились;
round3-accept-owner-branches-first.md — те же ветки по именам (Antigravity уже 6c5bf36);
round3-format-proof.md — prettier-only ветка проходит, 513cdff ловится;
round3-accept-all-open.md — все открытые codex/*, antigravity/* после fetch 16:16 NOVT;
round3-combined-merge.md — локальная сборка основа+live-database+c0(+grok): конфликтов нет,
интеграционные 7/7, храповик просит убрать ровно K3/N2, K4/N1, K13/SYNC-A, K13/SYNC-B, SYNC-C0;
round3-preset-mode.md — режим E2E_MIGRATOR_URL/E2E_APP_URL (вердикт SKIP).
Файлы прогнаны через prettier (иначе format:check в CI падает), содержимое таблиц не менялось.
Почему: TASK-GROK-3 п.5 / TASK-CODEX-2 п.2.2 — данные пилота живут под заглушкой
01J00000000000000000000000, перенос на 01M2R6AC5652NPT3MG34DKN4ZP нельзя проверить руками.
[PILOT-REHOME-SEED] (должен быть зелёным): таблицы с tenant_id берутся из information_schema,
в каждую сеется 2+ строки заглушки и постороннего арендатора; новая таблица без сида → красный.
[PILOT-REHOME]: --dry-run печатает те же числа и ничего не меняет; перенос админом БД; под
заглушкой 0 строк, счётчики и SUM(amount_minor) ledger совпадают, посторонний не тронут, запись
в staff_audit_log нового арендатора, krona_staff_app видит тех же сотрудников под новым tenant и
никого под старым, UPDATE staff_payroll_ledger отклоняется, повторный запуск отказывает.
Пока скрипта нет — красный с понятным сообщением, ID в known-defects.json.
Сценарий проверен временным эталоном (не коммитился): зелёный; мутации без аудита / без отказа
на повтор / с пропущенной таблицей / с записью в dry-run — красные (report-evidence/round3-pilot-rehome.txt).
client.ts: dbQueryAs(tenant, …) — запрос ролью приложения под заданным арендатором.
Почему: TASK-GROK-3 п.1 — на общем кластере (параллельные прогоны приёмки, чужой стенд)
пересоздание ролей krona_migrator/krona_staff_app со случайными паролями ломает соседей.
Если заданы оба URL, global-setup не вызывает provision и не требует E2E_PG_ADMIN_URL;
по умолчанию поведение прежнее. provision-pg получил --roles keep: пересоздать только БД,
не меняя паролей (нужно accept-branch для отдельной БД на ветку).
ctx.adminDatabaseUrl (E2E_DB_ADMIN_URL или E2E_PG_ADMIN_URL + БД стенда) — только для PILOT-REHOME.
Проверено: run-e2e с E2E_MIGRATOR_URL/E2E_APP_URL без E2E_PG_ADMIN_URL — 45 failed | 9 passed,
как на основе; повторный прогон на уже мигрированной БД — то же.
Зачем: во время приёмки обе ветки обновили force-push
(codex/fix-live-database ea68e12 → b289dd6, antigravity/fix-reject-device-shifts
795bba0 → 1df2019, теперь на f5ac179). Вердикт по старому SHA не должен
выдаваться за вердикт по новому — «на слово ничего не принимать».
Что: повторный прогон стенда на новых HEAD, результат дописан в
report-evidence/acceptance-*.txt, REPORT.md и NOTES. Итог тот же:
fix-live-database 41 red / 13 green (K3, K4, K13 зелёные),
fix-reject-device-shifts 43 red / 11 green (K13 зелёный).
Зачем: PR в Forgejo создаётся без API, описание изменений и доказательства
живут в репозитории; владелец просил обновлённый отчёт с таблицей
«ID владельца / мой ID — цвет — фактический вывод» и приёмочными таблицами
по веткам исполнителей (TASK-GROK-2, раздел «Отчёт»).
Что: что изменилось в круге 2, таблица по всем ID на f5ac179 (45 red /
9 green, храповик OK), приёмка fix-live-database (K3, K4 зелёные; K5, K6
подтверждены по существу), fix-format-baseline (F14 зелёный; находка —
«style»-коммит содержит исправление Antigravity), fix-reject-device-shifts
(K13 зелёный), статус Windows (не проверено: нет раннера), CI, N5, N13,
список «не проверено». Ссылки на evidence ведут в report-evidence/.
Зачем: TASK-GROK-2 §4 — исправления принимаются только по прогону стенда,
«на слово ничего не принимать»; исполнителям нужны ID владельца и
контракты для новых сценариев K8/K9.
Что:
- report-evidence/acceptance-*.txt: для antigravity/fix-reject-device-shifts
(795bba0), codex/fix-format-baseline (fd1ec3d), codex/fix-live-database
(ea68e12) — команда, итог vitest, таблица «ID — было — стало — вывод»
против основной ветки f5ac179 с тем же стендом (worktree, без коммитов в
их ветки). Итог: fix-live-database — K3/N2, K4/N1 зелёные, открылись K5 и
K6 по существу; обе ветки Codex — SYNC-A/B зелёные, потому что «style»-
коммит fd1ec3d содержит исправление Antigravity (после prettier 0 строк
diff с её веткой) — записано как находка для ревью.
- NOTES: таблица дефектов с двойными ID, строки K6–K9, S9, A4, N12-DATE,
контракты C4 (verify подписанной ведомости) и C5 (params пресета), CI
(windows, артефакт evidence), раздел приёмки. Ни одна ветка не влита в
основную, поэтому known-defects.json не сокращён.
Зачем: на ветке codex/fix-live-database цепочка подтверждение → расчёт →
утверждение впервые дошла до конца, и K3/N2 остался красным не из-за
дефекта, а из-за теста: 8 ч × 123,45 ₽ = 98760 коп., а расчёт округляет
строку до rounding_unit_minor (по умолчанию 100 коп., service.ts:695) и
начисляет 98800. Округление до рубля — настройка продукта, не дефект;
тест не должен от него зависеть.
Что: SYNTHETIC_RATE_MINOR 12345 → 12300 (123,00 ₽/ч), оплата смены
официанта в K9 111111 → 111100. Суммы остаются заведомо синтетическими.
Зачем: TASK-GROK-2 §3 — денежные дефекты K6–K9, S9, A4 и дата расчёта
были в AUDIT.md, но не покрыты стендом; без красных сценариев исправления
Codex нечем принимать.
Что (tests/e2e/50-money-and-errors.e2e.ts, всё красное и внесено в
known-defects.json):
- K6/D5: отклонение смены из подписанной ведомости → 409 или сторно.
Сейчас падает на предусловии: подтверждение смены 500 (K3).
- K7/D2: две записи начисления за один день (две точки) и повторный
импорт с другой суммой → итог = сумма по книге. Факт: 22200 вместо 33300.
- K8/D1: после подписи меняется ставка, затем проверочный пересчёт
(предполагаемый контракт C4 GET /payroll/runs/:id/verify). Блокирован K3.
- K9/D3: официант по пресету waiter_upsell: params.shift_rate_minor не
сохраняется (сам D3, до K3); далее начисление = оплата смены (после K3).
- S9: удаление точки с устройством мигратором → 23502 вместо удаления или
23503 (публичного API удаления нет — так же, как в live-sync.mjs владельца).
- A4: тела 500 не должны содержать SQLSTATE и текст PostgreSQL.
- N12-DATE: month=2026-13 → 400 (факт 500, 22008); 2026-02-30 уже 400.
Прогон run-e2e: 45 red / 9 green, храповик OK.
Зачем: аудит владельца (docs/audit/2026-09-26/AUDIT.md) стал основным
реестром дефектов; без сопоставления исполнители и приёмка говорят о
разных ID для одной ошибки (TASK-GROK-2 §2).
Что: теги переименованы по таблице владельца ([K3/N2], [K13/SYNC-A],
[S5/SYNC-C], [D6/OWN-01], [K17/OWN-10] и т. д.), ключи known-defects.json —
так же; N9–N12, F7, F10 остаются одиночными, как в таблице. Сверх таблицы,
по тексту AUDIT.md: K1/SYNC-I (анонимный доступ без токенов — это K1 на
/sync), K1/PILOT-1 (подстановки без :? в пилотном compose прямо названы в
K1), K2/PILOT-2 (NODE_ENV=development в пилоте — суть K2). SYNC-C0, SYNC-K,
OWN-03..06/08/09, PILOT-3 ID владельца не имеют. run-e2e принимает «/» в
теге. Прогон: 38 red / 9 green, храповик OK — те же сценарии, новые имена.
Зачем: tests/e2e/evidence/ перезаписывается каждым прогоном (логи серверов
с ULID и портами, JSON vitest), поэтому любой запуск стенда менял
отслеживаемые файлы и засорял дифф (приёмка владельца, TASK-GROK-2 п.1).
Что: tests/e2e/evidence/ убран из индекса (git rm --cached) и добавлен в
корневой .gitignore (его же читает prettier, поэтому форматировать
сгенерированные файлы больше не нужно — вызов prettier из run-e2e убран).
В CI задача e2e выгружает папку артефактом e2e-evidence
(actions/upload-artifact, закреплён SHA v4.6.2; Forgejo 9.0.3 поддерживает
v4). Разовые доказательства, которые скрипты не перезаписывают (N5 до/после,
замер N13), перенесены в tests/e2e/report-evidence/ — на них ссылается
REPORT.md.
Зачем: проект разрабатывается под Windows, а стенд там не стартовал:
spawn('npx') и spawn('node_modules/.bin/tsx') без shell падают с ENOENT,
потому что в Windows это .cmd-файлы (приёмка владельца, TASK-GROK-2 п.1).
Что: scripts/test/node-commands.mjs находит JS-точки входа через
require.resolve (tsx/cli, vitest.mjs, prettier.cjs) и всегда запускает их
текущим node (process.execPath). Сервер запускается одним процессом
`node --import tsx src/server.ts`: CLI tsx порождает дочерний node, и под
Windows kill() родителя оставил бы сервер-сироту на порту. baseEnv()
передаёт дочерним процессам PATH и под win32 системные переменные
(SystemRoot, TEMP и др.), без которых node на Windows не работает с сетью,
но не наследует секреты родителя.
Проверка на Linux: tests/e2e/harness/node-commands.test.ts (входит в
npm test) — команды начинаются с process.execPath, без .cmd/.bin/npx,
файлы существуют; исходники стенда не вызывают npx и node_modules/.bin.
Реальный прогон на Windows 11 — не проверено (нет Windows-раннера).
Зачем: 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 по этому замеру.
Зачем: владелец пометил N13 как «не воспроизведено», а в коде окно гонки
(SELECT ... FOR UPDATE по отсутствующей строке, затем INSERT ... ON CONFLICT
DO NOTHING со своим runId) видно. Нужен повторяемый замер на живом сервере
и реальной PostgreSQL 17, а не рассуждение.
Что: scripts/test/n13-race.mjs — 10/25/50 параллельных первых
POST /payroll/calculate, каждая попытка на новой точке, проверка статусов,
числа run_id, строк в staff_payroll_runs и «фантомных» run_id. Режим as-is
показывает, что сейчас всё упирается в N2 (500). Режим n2-bypass ставит
DEFAULT now() только в одноразовой БД скрипта (продукт и миграции не
меняются), чтобы увидеть саму гонку. Итог: 0 некорректных из 30 попыток;
причина (из кода) — случайная сериализация через upsert
ensureDefaultPayRules. Это записано в NOTES-FOR-EXECUTORS вместе с
рекомендацией явной блокировки и замечанием об утечке текста ошибок БД в 500.
Зачем: решения владельца (роли точки/бухгалтера/директора, пакет зарплаты
с подписью директора, неизменяемость после подписи, зарплата по точке
фактической смены, согласие на биометрию) должны стать исполняемыми
критериями приёмки до того, как 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.
Зачем: корневой compose.yaml уже проверяется tests/unit/deployment.test.ts,
а pilot-файл, который реально разворачивается у владельца, — нет. В нём
подстановки вида ${STANDALONE_MANAGER_TOKEN} без :? молча дают пустое
значение, а при пустых токенах standalone выдаёт права анониму (F2);
NODE_ENV=development в pilot отключает production-проверки config.ts.
- PILOT-1: каждая подстановка должна быть ${VAR:?сообщение};
- PILOT-2: ни один сервис не с NODE_ENV=development;
- PILOT-3: у app нет учётных данных мигратора (сейчас зелёный).
Файл в tests/e2e как *.e2e.ts: tests/unit вне разрешённых путей, а
красные пункты (дефекты deploy/, которые правят исполнители) идут через
known-defects.json и не ломают `npm run check`. Сейчас PILOT-1 и PILOT-2
красные: 14 подстановок без :?, app с NODE_ENV=development.
Зачем: бэкенд офлайн-синхронизации (миграция 0011) покрыт только
юнит-тестами без реальной БД. Сценарии на живом сервере показывают, как
терминал реально взаимодействует с сервером, и фиксируют границы
доверия к устройству.
Проверяется правильное поведение: устройство не подтверждает смены и не
утверждает табель сам (SYNC-A/B); начальная загрузка работает (SYNC-C0,
сейчас 500 «column status does not exist» — у staff_org_units нет
status) и не теряет сущности после 100-й (SYNC-C); правки из портала
видны в инкрементальном pull (SYNC-D); отметки уволенных и вне окна
времени отклоняются (SYNC-E/J); точка устройства сохраняется, чужая
отклоняется (SYNC-F); битая команда не роняет пакет (SYNC-G); CSRF и
анонимная регистрация устройств запрещены (SYNC-H/I); ack не принимает
курсор из будущего (SYNC-K). SYNC-L — позитивный контроль отзыва.
Зачем: каждый открытый дефект аудита должен иметь исполняемый сценарий
на реальной БД, который проверяет ПРАВИЛЬНОЕ поведение. Тогда красный
тест = открытый дефект, а исправление видно по смене цвета, без ручной
перепроверки.
- N1 POST /employees -> 201; N2 подтверждение смены, расчёт, утверждение
и сумма начисления 8 ч x синтетическая ставка; N4 пересекающиеся
периоды не начисляют смену дважды (сейчас блокирован N2 — это видно в
сообщении assert как «предусловие, см. N2»);
- N3 SSRF: подписка на https://127.0.0.1:<порт> должна отклоняться, и
локальный слушатель не должен получить соединение;
- N9, N12, N10, N11, F2 (процесс без токенов), F3, F4 (отдельный процесс),
F7, F10.
Сотрудники для сценариев, не относящихся к N1, создаются импортом, чтобы
один дефект не маскировал остальные. Все данные синтетические (E2E-*),
ставка 123,45 ₽/ч заведомо не реальная.
Зачем: 156 зелёных тестов на fakeDatabase не поймали, что ключевые
сценарии (создание сотрудника, подтверждение смены, расчёт зарплаты)
всегда падают на реальной PostgreSQL. Нужен приёмочный стенд, который
проверяет продукт как чёрный ящик: настоящий процесс сервера, настоящая
БД, роли как в pilot, но с минимальными привилегиями.
- tests/e2e/harness/global-setup.ts: создаёт БД и роли через
scripts/test/provision-pg.mjs, применяет миграции ролью krona_migrator
(NOSUPERUSER), поднимает `tsx src/server.ts` под krona_staff_app с
токенами/VAPID из crypto.randomBytes; три процесса: с токенами, без
токенов (как корневой compose.yaml) и отдельный для проверки rate limit;
логи серверов пишутся в tests/e2e/evidence/.
- *.e2e.ts и отдельный vitest.e2e.config.ts: e2e не попадают в `npm test`,
поэтому `npm run check` не требует БД и не краснеет от известных дефектов.
- [HARNESS]/[CTRL] — позитивные контроли (версия PG >= 17, атрибуты ролей,
app-роль не видит schema_migrations, RBAC, уволенный теряет доступ):
если они красные, красные дефектные сценарии ничего не доказывают.
- scripts/test/run-e2e.mjs: «храповик» известных дефектов по
tests/e2e/known-defects.json (новый красный или исправленный-но-не-
вычеркнутый ID = exit 1), таблица e2e-table.md и проверка, что в
evidence нет ничего похожего на секреты стенда.
Логи названы *.txt, т.к. *.log в .gitignore.
Зачем: файл уже до правки N5 не проходил prettier --check (часть F14); форматирование вынесено в отдельный коммит, чтобы смысловая правка N5 читалась отдельно от шума.
Зачем: afterAll удалял строки соединением роли приложения, а у
krona_staff_app намеренно нет DELETE на staff_employees. На реальной
PostgreSQL 17 тест проходил, но файл падал в afterAll ("permission denied
for table staff_employees", exit 1), поэтому любой CI с DATABASE_TEST_URL
был бы красным не из-за дефекта продукта, а из-за самого теста.
Выдавать app-роли DELETE ради теста нельзя: это ослабило бы модель прав.
Что сделано:
- очистка идёт отдельным соединением роли мигратора
(DATABASE_TEST_MIGRATION_URL или DATABASE_MIGRATION_URL, который CI уже
задаёт); так как FORCE RLS действует и на владельца таблиц, в транзакции
выставляется app.tenant_id; удаляются строки обоих тестовых tenant;
- очистка выполняется и в beforeAll, чтобы остатки прошлого упавшего
прогона не ломали уникальные ключи (EMP-PUSH-1, DEV-A);
- без URL мигратора тест падает с понятной ошибкой, а не оставляет мусор.
До: tests/e2e/evidence/n5-before.txt (1 passed, suite FAIL, exit=1).
После: tests/e2e/evidence/n5-after.txt (два прогона подряд, exit=0).
Зачем: аудит показал, что 156 зелёных тестов на fakeDatabase не заметили,
что создание сотрудника, подтверждение смены и расчёт зарплаты всегда
падают на реальной PostgreSQL. Чтобы тесты и CI проверяли реальный SQL,
нужен воспроизводимый стенд с ролями как в pilot, но с минимальными
привилегиями:
- scripts/test/provision-pg.mjs: bootstrap-админ (только как DBA/entrypoint)
создаёт krona_migrator (NOSUPERUSER NOCREATEDB NOCREATEROLE, владелец БД и
схемы public) и krona_staff_app (NOSUPERUSER NOBYPASSRLS NOINHERIT); пароли
crypto.randomBytes, пишутся только в env-файл 0600, в вывод не попадают.
scripts/create-app-role.mjs здесь не используется, т.к. без SUPERUSER он
падает (находка N6, продуктовый код не трогаем).
- scripts/test/migrate-roundtrip.mjs: up -> откат всех миграций -> up ->
повторный up должен быть no-op; ролью мигратора, как требует AGENTS.md.
Проверено на PostgreSQL 17.11: 11 миграций применены, откачены, применены
повторно, третий up пустой.