krona-staff/docs/audit/2026-09-26/TASK-ANTIGRAVITY-2.md
Victor Trushenko f5ac179623
Some checks are pending
ci / verify (push) Waiting to run
docs(audit): add 2026-09-26 audit, acceptance and executor tasks
Executors could not see the audit: Grok reported that the owner's
AUDIT.md and evidence never reached it, so its scenarios could not be
mapped to the audit IDs. Keeping the audit, the acceptance report and
the task for each executor in the repository lets Codex, Antigravity
and Grok take work and report through git.

The live-check scripts are stored as .mjs.txt so the project linter,
which type-checks every .mjs file, does not break for everyone; the
maintained version of these checks is Grok's harness in tests/e2e.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 15:13:56 +07:00

19 KiB
Raw Permalink Blame History

Задание для Antigravity, круг 2: синхронизация, push, outbox

  • Репозиторий: https://git.ochenstarik.ru/ochenstarik/krona-staff
  • Основа: текущая основная ветка codex/bootstrap-platform-foundation (331e490)
  • Задание самодостаточное; полный аудит и доказательства — в docs/audit/2026-09-26/.

Итог приёмки прошлого круга

Ветку antigravity/fix-reject-device-shifts (795bba0) проверили на живой PostgreSQL 17.

Что сработало:

  • Подделка смены с устройства закрыта. Смена status: confirmed, worked_minutes: 2880 теперь получает rejected / COMMAND_NOT_SUPPORTED. Раньше она начисляла 14 400 ₽, теперь 0 ₽.
  • Сценарии Grok SYNC-A и SYNC-B на этой ветке зелёные.

Что не так:

  1. tests/integration/sync-rejection.test.ts:69-74 на реальной базе падает. Очистка идёт ролью приложения, а у неё нет права DELETE на staff_timesheet_entries и staff_employees. Проверка внутри теста проходит, но файл красный — значит, на живой базе тест не запускали.
  2. Сообщение коммита — один абзац в строке заголовка. Нужен короткий заголовок, а причина — в теле.
  3. Из задания сделана только немедленная мера. Остальные части не начаты.

Как работать в этом круге

  • Ветки. antigravity/fix-<тема> от 331e490, по одной на каждый пункт ниже. Маленькие коммиты: заголовок до 72 символов, причина в теле (AGENTS.md).

  • Проверка на живой базе обязательна. В репозитории есть стенд Grok (ветка grok/e2e-harness, после влития — в основной):

    • scripts/test/provision-pg.mjs создаёт роли как в пилоте: мигратор без SUPERUSER, приложение без BYPASSRLS;
    • node scripts/test/run-e2e.mjs прогоняет сценарии на реальном сервере и реальной PostgreSQL 17.

    Пункт считается сделанным, когда соответствующий сценарий стал зелёным. Номера сценариев указаны в таблицах.

  • Очистка в тестах — ролью мигратора, как в tests/integration/tenant-isolation.test.ts после исправления Grok. Роли приложения удалять запрещено, так задумано.

  • Список известных дефектов. После каждого исправления сообщи Grok ID сценариев, которые стали зелёными, — он уберёт их из tests/e2e/known-defects.json. Сам этот файл не правь: иначе CI упадёт с «дефект исправлен, обновите список».

  • Outbox. Писать в staff_outbox только через общий хелпер, который делает Codex (ветка codex/fix-live-database). Во вставке обязательно поле occurred_at — без него подтверждение и расчёт падают.

Твоя зона:

  • src/sync.ts, src/domain/sync/**, миграции синхронизации;
  • src/domain/notification/**, маршруты /push/*;
  • service worker и подписка в src/portal-ui.ts;
  • диспетчер и публикатор outbox.

Не трогай: авторизацию, сотрудников, смены, расчёт, терминал и развёртывание — это зона Codex; tests/e2e/** и CI — зона Grok.

1. Довести прошлую ветку (сегодня)

Что сделать Готово, когда
Очистка в sync-rejection.test.ts — ролью мигратора Тест зелёный на живой базе
Перебазировать ветку на 331e490, переписать сообщение коммита Ветка вливается в основную без конфликтов

2. Синхронизация на реальной базе не работает (срочно)

ID Что сломано Где Готово, когда
SYNC-C0 Начальная загрузка на устройство (/sync/pull без курсора) всегда отвечает 500: column "status" does not exist. Запрос обращается к столбцу, которого в таблице нет src/domain/sync/service.ts, начальная загрузка SYNC-C0 зелёный

3. Синхронизация: остальные дефекты

Мой ID Сценарий Grok Что сделать Готово, когда
S2 SYNC-J occurred_at — строгий ISO с часовым поясом. Устройство передаёт своё текущее время, сервер вычисляет расхождение часов и ограничивает время отметки относительно момента получения. При большом расхождении — pending_review. Сейчас отметка с датой 2020 года принимается SYNC-J зелёный
S3 SYNC-G Сначала захватить ключ идемпотентности, потом выполнять команду. SAVEPOINT на каждую команду. Проверять ревизию в UPDATE. source_event_id = sync:{device}:{key}. Временные отказы не кэшировать Битая команда не откатывает весь пакет; две параллельные отправки с одним ключом выполняются один раз
S4 SYNC-E, SYNC-F Начальная загрузка — только по точке устройства, только активные сотрудники, ограниченное окно дат, пагинация в SQL. Отметки уволенных отклонять. Точку брать из устройства; чужую точку в payload отклонять SYNC-E и SYNC-F зелёные
S5 SYNC-C, SYNC-D Курсор не теряет данные при has_more. Порядок — по фиксации транзакций, а не по BIGSERIAL. Изменения из портала видны устройству. Устаревший курсор запускает повторную начальную загрузку SYNC-C и SYNC-D зелёные
S6 — Отметки через синхронизацию пишут outbox (через общий хелпер), как онлайн-маршрут Отметка через синхронизацию порождает событие
S7 — Очистка входящих получает tenant_id, а не идентификатор системного актора (service.ts:1056), и запускается по расписанию —
S8 SYNC-H Проверка Origin на маршрутах управления устройствами, белый список scope, no-store на ответе с токеном устройства SYNC-H зелёный
S9 попроси Grok добавить Внешний ключ (tenant_id, org_unit_id) ON DELETE SET NULL обнуляет и tenant_id, поэтому удаление точки с привязанным устройством падает с 23502. Новой миграцией заменить на SET NULL (org_unit_id) или RESTRICT Точку можно удалить или получить понятный отказ
— SYNC-I Без настроенных токенов анонимный запрос не должен регистрировать устройство. Сделать вместе с Codex: он закрывает открытый режим без токенов (F2) SYNC-I зелёный
— SYNC-K Подтверждение курсора «из будущего» (больше выданного сервером) отклонять с 400 SYNC-K зелёный

4. Push-уведомления

Мой ID Сценарий Grok Что сделать Готово, когда
K12 N3 SSRF: сейчас сотрудник подписывает push на https://127.0.0.1:…, и сервер сам туда подключается. Разрешать только известные push-сервисы (FCM, Mozilla, Apple, WNS). После DNS запрещать приватные, loopback и link-local адреса. redirect: 'manual', таймаут 5–10 с, тело ответа не сохранять. Где: push-dispatcher.ts:80, web-push.ts:226-258 N3 зелёный
N9 N9 Конфликт endpoint с другим сотрудником — ответ 409. Перепривязка разрешена только тем же браузером N9 зелёный
N8 — В production без VAPID_* сервер не стартует. Публичный ключ отдавать с no-cache —
U1–U4 — Проверять ключи подписки при регистрации. Ошибка по одной подписке не прерывает отправку остальным. Не больше 5–10 подписок на сотрудника. Таймаут и лимит чтения ответа 1 КБ. Атомарный захват строки перед отправкой (sending плюс аренда), повторы с backoff, dead letter Два экземпляра сервера не отправляют сообщение дважды
U5–U7 — Scope service worker покрывает /staff. Выход отзывает push-подписку устройства. Составной внешний ключ с tenant_id в staff_notification_deliveries —

5. Оповещения процесса подтверждения (после этапа 4 у Codex)

Решение владельца: бухгалтер отправляет пакет расчёта и выплат на подтверждение, руководитель получает оповещение, проверяет и подписывает, после чего всё регистрируется. Сам процесс и события делает Codex, от тебя — доставка.

Событие Кому Содержание
staff.payroll.package_submitted Всем с ролью «Руководитель» в арендаторе Период, число сотрудников, итог, число предупреждений, ссылка на экран подписи
staff.payroll.package_returned Бухгалтеру, который отправил пакет Комментарий руководителя
staff.payroll.package_signed Бухгалтеру и сотрудникам из пакета Сотрудникам — только «начисление за период доступно», без сумм

Основной канал — push. Запасной — Telegram, без персональных данных и сумм. Если пакет не подписан за 24 ч (значение настраивается), отправить повторное напоминание. Адресата определять по роли, а не по фиксированному списку.

Сценарий Grok OWN-03 («каждый руководитель получает ровно одно оповещение») должен стать зелёным.

6. Публикатор outbox

Вместо setImmediate нужен фоновый публикатор:

  • повторяет строки в статусах pending и failed;
  • ставит published_at;
  • на стороне получателя — идемпотентность через inbox;
  • соблюдает политику хранения.

Готово, когда после перезапуска сервера неотправленные события уходят, а staff_outbox не растёт бесконечно.

Порядок

  1. Пункт 1, затем пункт 2 — сегодня.
  2. Пункты 3 и 4 — параллельно, по одной ветке на пункт.
  3. Пункт 5 — когда Codex выпустит события пакета.
  4. Пункт 6 — после влития общего хелпера outbox от Codex.

Отчёт после каждой ветки

Ветка, коммит и ID сценариев Grok, которые стали зелёными, — с фактическим выводом run-e2e.