--- title: "sched F&F r8 — стенд верификации (schedule-as-entity)" type: concept tags: [sched, fnf, verification, stand, verdaccio, schedule-as-entity, docker] sources: [] related: [concepts/windows-docker-test-harness-gotchas.md] updated: 2026-08-18 --- # sched F&F r8 — стенд верификации Кросс-агентный цикл F&F (findings & fixes): sched-команда публикует пакеты в verdaccio, admin-сессия верифицирует на стенде и репортит находки через инбокс (`sched/.agents/inbox/`). Раунд r8 = «schedule-as-entity» (движок тикает по schedule-строкам). Цикл идёт письмами: intro → отчёт → фикс → перепроверка → следующий пункт. ## Стенд - Путь: `C:\Users\vitya\projects\.admin\.tmp\sched-r8\` (gitignored, живёт на диске). - package.json: **@sched/core 0.40.2 / daemon 0.3.0 / storage-{mysql,postgres,mongo} 0.3.1 / ui 0.2.0** из verdaccio (`npm install --registry=https://verdaccio.kzntsv.site/`). `legacy/` — старый core 0.35.1 для генерации v0.6-БД. - Docker-контейнеры (как в CI sched): `sched-mysql-test` :3308, `sched-mariadb-test` :3307, `sched-postgres-test` :5433, `sched-mongo-test` :27017; root:test / postgres:test. ## Репро-скрипты (15 шт., `.tmp/sched-r8/*.mjs`) **Миграции / адаптеры (№1):** - `migration-test.mjs` — sqlite v0.6→v0.7 (эталон синтеза) - `adapter-migration-test.mjs` — F2: mysql/pg v3→v5, синтез легаси-строки, due стреляет - `mongo-synthesis-test.mjs` — F2 mongo: first-open синтез, no-dup, non-empty gate. Легаси-док: коллекция `sched_tasks`, `config.data` (не `data`), `paused`/`disabled` обязательны (иначе null → не в due) - `priority-order-test.mjs` — F1: task priority 10 наследуется на строку - `adapters-spotcheck.mjs` — 12/12 CRUD+dedupKey-upsert на 4 адаптерах - `tasks-json-test.mjs` — модель tasks.json (schedules:[...], fail-fast legacy key) - `contract-parity.test.mjs` — vitest contract-сьют 5 адаптеров (см. гочу №2) **Движок (№2, 75 проверок):** - `engine-runs-test.mjs` (16) — scheduleId/data/trigger на schedule-ранах; manual (null+дефолты); retryRun (снапшот+retryOf) - `engine-due-claim-test.mjs` (10) — priority-порядок, ceiling с блокирующим раннером, pause, once, catch-up - `engine-retry-test.mjs` (27) — ретраи на строках (retryCount/backoff×multiplier/failCount/onRunFinal), override, retryOf-цепочка - `engine-watchdog-test.mjs` (14) — reap обоих наборов локов, heartbeat real-clock, recoverOrphanRuns - `engine-async-test.mjs` (8) — async poll через schedule-раны, poll timeout **Политики (№3, 21 проверка):** - `engine-policy-test.mjs` — whole-no-merge (override не наследует поля дефолта), snapshot-семантика пендинг-ретрая, pause AND два уровня, manual=дефолты задачи, priority-микс **Admin API (№4, 52 проверки):** - `admin-api-test.mjs` — живьём по HTTP против `createAdminApi` (in-process, listen(0), fetch): POST /schedules dedupKey-upsert 201/200 (UUID id, fileManaged=false, sync не дизаблит), tz ?? task.tz, GET /schedules first-class + lastRunStatus + effectiveStatus (no N+1 — ровно 3 чтения через счётчик storage-методов), GET/DELETE /:id (раны переживают delete), pause/resume (гейтит только строку), hard cut POST /tasks/:name/schedule → 404 с указателем, POST /tasks runtime (fileManaged=false, переживает sync, state сохраняется), PATCH merge, MCP list_schedules (runTool → новая форма). **UI (№5, 13/13 зелёных + U1/U2):** - `ui-backend.mjs` — бэкенд :8080 (createAdminApi, монтируется на /api/* как daemon — sched-ui serve проксирует путь verbatim), датасет books-паттерна: 4 строки × (active/failed, paused-schedule/succeeded, paused-task/без ранов, runtime-строка) + auth по SCHED_ADMIN_KEY. - `sched-ui serve --proxy http://127.0.0.1:8080 --port 8081` + **браузерный CDP-стенд** (`~/projects/.common/scripts/browser/{start,nav,eval,screenshot}.js`): рендер таблицы (shadowRoot-инспекция), клики по форме create/edit/delete, перехват window.fetch для проверки body POST/PATCH/DELETE, мок window.confirm для delete, замер геометрии (getBoundingClientRect). ## Что подтверждено (контракт) - **Ceiling сериализует через тики**: две due-строки одной задачи стреляют в РАЗНЫХ тиках (sibling-claim отклоняется атомарно). Параллелизм только между задачами. - **onRunFinal** — на любом терминальном ране (успех тоже), не только на persistent-failure; «алерты только на фейлы» — фильтр consumer'а. - **retryCount на строке** = consumed-попытки (кумулятив: после 2 фейлов = 2). - **resolveSchedulePolicy** = `schedule.retry ?? task.retry` целиком; explicit `priority: 0` уважается как есть. - **Snapshot-семантика**: правка политики не пересчитывает дедлайн пендинг-ретрая; свежая политика — со следующего диспатча. - **pause AND**: `task.paused` гейтит все расписания (движок), `schedule.paused` — одно (due-запрос). - **manual triggerTask** = дефолты задачи (data, без schedule-политики), расписание не двигается. - **F1 (core 0.40.2)**: inner cron timezone ХОСТИТСЯ (`schedules.timezone ?? task/API tz ?? UTC`), конфликт-правило удалено — задача с tz может использовать другой inner timezone (доки были правы, код врал). - **F3 (core 0.40.2)**: PATCH /schedules/:id = partial merge (RFC 7396-стиль): меняются только поля в теле, явный `null` очищает, опущенное сохраняется — dedupKey не осиротевает при правке одного правила. ## Находки №5 (UI, ждут разбора) - **U1**: форма sched-schedules кладёт значение tz-поля ВНУТРЬ schedule-entry как `timezone` (валидно только для cron), а не как body-level `tz` → interval/once с tz нельзя ни создать, ни отредактировать (400 «timezone is only valid on cron schedules»); edit любой interval-строки с body-tz сломан (форма предзаполняет tz из строки). - **U2**: таблица sched-schedules переполняет контейнер на ~58px (1076 vs 1018 при длинных JSON в rule/data) — actions/delete за правым краем карточки. Общий стиль `table{width:100%}` + auto-layout без `table-layout:fixed`/`word-break` на td; `overflow:hidden` на table не спасает. `sched-tasks` (7 колонок) — ровно 1018px. ## Как продолжить (закрытие раунда) 1. Бамп версий в package.json стенда → `npm install --registry=...` (после каждого фикса). 2. Новые проверки — отдельный `.mjs` по паттерну существующих (`check()` + SUMMARY), изолированные DB на сценарий; UI — браузерный CDP-стенд (см. выше). 3. Отчёт — письмо в `sched/.agents/inbox/` с фронтматтером `from: admin`, формат «делал → ожидал → получил → раздел доки». Не чинить, не пушить, их доску не трогать. 4. №1–№4 ПРИНЯТЫ (52/52 после фиксов), №5 репорт ушёл (13/13 + U1/U2) — ждём разбор → закрытие раунда (процедура fnf-testing-procedure пополнится результатами).