Полная проверка с локальным Dee Wan
Оба сервиса запускаются на одной машине, без учётной записи Cloudflare и рабочих учётных данных. Установите плагин в локальный Dee Wan, запланируйте и выполните публикацию, затем проверьте предусмотренные сценарии ошибок.
README плагина — справочник по маршрутам, конфигурации, семантике расписаний и таблице повторов.
Здесь — последовательность действий. Документация со стороны ядра находится в
docs/plugins/ репозитория Dee Wan —
protocol v1, безопасность,
тестирование.
Для этих действий не нужны реальный домен, рабочий токен или развёрнутый Worker. Не выполняйте их на рабочем сайте.
1. Настройка обоих сервисов
Заголовок раздела «1. Настройка обоих сервисов»Следуйте разделу Локальная конфигурация из двух сервисов в «Тестировании плагина», используя этот плагин. Когда закончите:
- admin Worker Dee Wan работает на
http://localhost:8787, а его admin UI — наhttp://localhost:5173, сPLUGIN_DEV_ORIGINS=http://localhost:8788вbackend/.dev.vars, иlocal:seedвывел данные для входа. - этот плагин работает под
npx wrangler dev --port 8788 --test-scheduledизplugins/scheduling, с применёнными миграциями локальной D1 иADMIN_SECRET=local-admin-secret-0123456789в.dev.vars. - у вас есть свежий код активации из
POST /admin/activation-codes. Он одноразовый и истекает через 15 минут, поэтому выпустите другой, если он просрочен.
Команды ниже выполняются из plugins/scheduling.
2. Установка в админ-интерфейсе
Заголовок раздела «2. Установка в админ-интерфейсе»Войдите по адресу http://localhost:5173 с учётной записью, которую вывел local:seed, затем
откройте Настройки → Плагины → Установить:
- URL манифеста
http://localhost:8788/dee-wan/manifest.json. - код активации из шага 1.
- возможности: отметьте все пять для этого прогона. Сначала все флажки сняты.
- модели: отметьте
Article(или то, что есть на сайте, заполненном тестовыми данными).
Используйте UI, а не curl: административным маршрутам нужны настоящая сессия, заголовок сайта и
право site:plugins, а у UI есть все три.


Что происходит, по порядку: Dee Wan получает и валидирует манифест, записывает ожидающую инсталляцию,
выпускает токен, отправляет его POST-запросом вместе с вашим кодом на http://localhost:8788/dee-wan/activate и помечает
инсталляцию активной только после того, как этот плагин ответит 2xx. Проверьте сторону плагина:
npx wrangler d1 execute dee-wan-scheduling --local \ --command "SELECT id, site_id, dee_wan_base_url FROM installation"В столбце token_ciphertext хранится зашифрованный токен. Ни на одной из сторон нет колонки, строки лога или
маршрута, которые покажут вам сырой токен.
3. Планирование публикации
Заголовок раздела «3. Планирование публикации»В Dee Wan: откройте черновик записи в этой модели и используйте действие Schedule, объявленное манифестом.
Это делегированный запуск: Dee Wan выпускает одноразовый код, ваш браузер попадает на
http://localhost:8788/installations/<id>?dee_wan_launch=…, бэкенд этого плагина обменивает его на
делегирование, устанавливает собственный cookie сессии и перенаправляет на ту же страницу без кода в URL.
Форма читает элемент через делегирование, поэтому предлагаемые ею переходы — это те, которые вы
можете выполнить прямо сейчас. Выберите локальное время на минуту-две вперёд и часовой пояс. Подтверждение показывает
и локальное время с его поясом, и соответствующий момент в UTC. Именно эта пара
сохраняется. С поясом UTC они совпадают, поэтому показано одно значение.
npx wrangler d1 execute dee-wan-scheduling --local \ --command "SELECT id, kind, target_state, pinned_version_id, expected_workflow_revision, local_datetime, timezone, due_at, status FROM schedule"pinned_version_id — точная версия, которую это опубликует, а не «то, что будет текущим в момент выполнения».
4. Запуск выполнения
Заголовок раздела «4. Запуск выполнения»Cron-триггер в wrangler dev сам по себе не срабатывает. С --test-scheduled:
curl "http://localhost:8788/__scheduled?cron=*+*+*+*+*"Один тик захватывает до 20 записей, срок которых наступил, с временной блокировкой, отправляет по одной команде на каждую и завершает их. Затем:
npx wrangler d1 execute dee-wan-scheduling --local \ --command "SELECT id, status, attempt_count, last_code, last_message FROM schedule"npx wrangler d1 execute dee-wan-scheduling --local \ --command "SELECT schedule_id, attempt, code, http_status FROM schedule_attempt ORDER BY attempt"Завершённое расписание показывает status = completed, last_code = transition_applied. В Dee Wan
элемент теперь опубликован, состояние всей группы переводов изменилось одновременно, а история контента показывает
Scheduling plugin в качестве действующего лица — не вашу учётную запись и не выдуманную.
Тик до наступления срока ничего не захватывает. После наступления срока первый тик завершает
расписание, а второй тик не находит работы, потому что захватываются только строки pending или retrying, срок выполнения которых наступил (и
строки running, срок блокировки которых истёк), а эта теперь completed. Если бы одна и та же
команда когда-либо была отправлена дважды — скажем, после сбоя Worker, когда задачу повторно забрал другой исполнитель, — ключ идемпотентности
заставляет Dee Wan ответить idempotent_replay вместо повторной публикации.
Чтобы увидеть это в календаре: Календарь в разделе Обзор или в списке Контент, либо Настройки →
Плагины, затем Открыть на карточке Calendar в строке Scheduling. Это запуск manage с
"view": "calendar". Ему не нужно site:plugins, нужна лишь одна выданная Scheduling модель, которую
вы читаете: все записи или только свои. Человек, который читает, но не может публиковать, видит тот же календарь без
Reschedule и Cancel. Человек, который читает только свои записи, видит только их расписания.
URL запуска содержит
dee_wan_view=calendar рядом с кодом, и после обмена плагин перенаправляет на
/installations/<id>/calendar. В ячейке дня указано число запланированных операций; ссылка дня показывает
расписание как Completed с transition_applied. Выберите в фильтре другой часовой пояс, чтобы увидеть
ту же строку в местном дне этого пояса.
4a. Запуск релиза
Заголовок раздела «4a. Запуск релиза»Откройте Настройки → Плагины, затем Открыть на карточке Releases плагина Scheduling (запуск manage с view: releases). На панели управления также есть Релизы. Создайте Launch и добавьте две записи разных моделей через Add to release на карточках Schedule. Выберите публикацию и язык либо всю группу переводов. В релизе не больше 25 элементов.
Для обычного элемента оставьте Version → Latest ordinary draft. Сохраните новый обычный черновик одной записи и обновите релиз. Он готов отправить последний сохранённый черновик: добавление элемента не закрепляет версию. Если элемент заблокирован, Run now отключена с указанием причины. Недоступный для чтения элемент блокирует запуск без раскрытия названия. Исправьте или удалите его. Запланированный релиз, заблокированный в своё время, сохраняет Blocked attempts и ждёт ручного запуска или нового времени.
Во втором релизе при добавлении публикации выберите Version → Independent future version. В Dee Wan откройте Будущие версии, выберите этот релиз, измените заголовок или секции страницы и нажмите Сохранить. Проверьте История будущей версии и убедитесь, что обычный черновик сохранил прежний контент. Задайте время релиза, затем откройте Предпросмотр до этого выпуска (у составных страниц предпросмотр находится в редакторе страницы). Он учитывает опубликованный контент и более ранние релизы. Без времени возвращается release_time_required. Пересекающемуся изменению той же записи на том же языке задайте другое время, чтобы избежать release_time_ambiguous.
Запустите будущий релиз и убедитесь, что его ветка закреплена. Публикация должна использовать именно эту версию с её медиа и пройти проверку текущих прав, обязательных полей и задач. При потере ответа повторите записанную команду, а не выбирайте новый черновик. Обычный черновик не меняется. Для автоматизации служит timeline-preview; в release-edit этот режим задаётся через add_future, а попытка снятия с публикации отклоняется с future_requires_publish.
Нажмите Run now. Исполнитель сохраняет версии и ревизии workflow всех элементов и отправляет одну атомарную команду /releases/commit. Применяются все изменения публикации или ни одно. Проверьте квитанцию и строки локальной базы:
curl "http://localhost:8788/__scheduled?cron=*+*+*+*+*"npx wrangler d1 execute dee-wan-scheduling --local \ --command "SELECT instance_id, status, attempt, published_version_id, last_code FROM content_release_item"Релиз показывает Done; квитанция содержит release_applied и применённую версию каждой опубликованной записи.
Для проверки отказа добавьте снятие с публикации записи, на которую ссылается опубликованная запись вне релиза. Ядро возвращает dependent_content, релиз показывает Failed: ни снятие, ни другая публикация не применяются. Устраните зависимость и нажмите Retry unfinished items. После определённого отказа новая попытка читает свежие версии и использует ключ rel:<release>:a2.
Также опубликуйте общую секцию и использующую её страницу одним релизом в обоих порядках элементов. Проверьте два разных языка одной группы переводов. Повтор языка и пересечение всей группы с отдельным языком должны отклоняться.
Потерянный ответ не означает отказ или частичный успех. Исполнитель повторяет всю сохранённую команду с тем же ключом. Если после предела попыток результат неизвестен, отображается Outcome unknown; повтор получает результат этой команды, не угадывая успех элементов.
Для планирования задайте дату, время и часовой пояс и нажмите Schedule. Тик до этого времени ничего не меняет; тик после него проверяет релиз и отправляет одну команду. Unschedule возвращает черновик. Stop remaining items останавливает ещё не отправленную работу. Уже внесённые изменения сохраняются; отправленную команду отменить нельзя.
Те же действия доступны через операции плагина releases, release-edit, release-run в API управления, CLI и MCP. Для этих операций нужен site:plugins.
5. Намеренные сбои
Заголовок раздела «5. Намеренные сбои»Каждый из них — реальное состояние, с которым столкнётся пользователь. Выполняйте их на тестовом сайте.
| Чтобы увидеть | Сделайте это | Ожидайте |
|---|---|---|
scheduled_target_stale |
Запланируйте публикацию, затем отредактируйте и сохраните черновик, затем выполните тик | refused, без повтора, и элемент остаётся неопубликованным |
workflow_conflict |
Запланируйте публикацию, затем опубликуйте и снимите с публикации элемент вручную, затем выполните тик | refused, без повтора |
transition_unavailable |
Запланируйте публикацию, затем опубликуйте элемент вручную, затем выполните тик | refused. Dee Wan проверяет переход раньше ревизии, а опубликованный элемент не предлагает публикации |
dependent_content |
Опубликуйте A, свяжите с ним опубликованный B, запланируйте снятие с публикации A, выполните тик | refused, и Dee Wan называет зависимость |
requester_unauthorized |
Запланируйте как пользователь, уберите роль этого пользователя в Dee Wan, выполните тик | refused. Права проверяются заново при выполнении; сохранённые ранее права не используются |
plugin_unauthorized |
Запланируйте, затем отключите или удалите в разделе Настройки → Плагины, выполните тик | refused, сохранено с last_code = plugin_unauthorized. Страницы плагина пишут, что инсталляция удалена или отключена, и не показывают расписаний |
| транспортный сбой | Остановите admin Worker Dee Wan (local:api), выполните тик |
retrying с last_code = no_response и next_attempt_at. Перезапустите его и выполните тик после этого времени, чтобы завершить |
retry_exhausted |
Остановите Dee Wan и выполняйте тики сверх MAX_ATTEMPTS или позже due_at + MAX_LATENESS_MS |
refused retry_exhausted, и больше ничего никогда не отправляется |
| отмена | Отмените ожидающее расписание в UI плагина | cancelled. Dee Wan никогда не вызывается, и ничего не откатывается |
| выполняющееся расписание | Отмените, пока тик в процессе | execution_in_progress, и UI перезагружает итоговое состояние |
Задайте MAX_ATTEMPTS=2 и MAX_LATENESS_MS=60000 в .dev.vars и перезапустите wrangler dev, чтобы достичь
лимитов за минуту, а не за четверть часа.
Остановленный Worker — это транспортный сбой, а не temporary_failure: ответа нет вообще, поэтому
нет и кода исхода. temporary_failure — это ответ: Dee Wan отправляет его как структурированный
исход только с 503, а плагин записывает под этим кодом и HTTP-статус 500, 502, 503 или 504 без структурированного исхода.
Локальный Dee Wan не выдаёт ни того, ни другого по требованию. Собственный набор тестов плагина проверяет этот случай с подставленным
ответом.
После retry_exhausted, последовавшего за транспортным сбоем, исход действительно неизвестен —
команда могла быть применена Dee Wan, который не смог ответить. Проверьте элемент в Dee Wan.
Именно поэтому расписание так и говорит, а не заявляет о сбое.
6. Ротация и удаление
Заголовок раздела «6. Ротация и удаление»Ротация, которую оператору стоит отрепетировать до того, как она понадобится:
curl -X POST -H "Authorization: Bearer local-admin-secret-0123456789" \ -H 'content-type: application/json' \ --data '{"installation_id":"<installation id>"}' \ http://localhost:8788/admin/activation-codesЗатем откройте Настройки → Плагины и выберите Заменить, указав этот код. Dee Wan выпускает новый токен, передаёт его сюда и только затем заменяет свой сохранённый хеш. Старый токен остаётся действительным в течение ограниченного периода совместного действия токенов, а первый запрос с новым токеном завершает этот период. Если этот плагин отказывает, старый токен продолжает работать — проверьте и это, выполнив ротацию с кодом, который вы уже использовали.
Удаление в Dee Wan отзывает токен немедленно и окончательно. Данные этого плагина остаются: любое
оставшееся расписание отказывает с plugin_unauthorized, когда наступает его срок. Страницы плагина перестают
показывать расписания и вместо этого пишут, что инсталляция удалена из Dee Wan и её расписания больше не выполняются:
без ответа Dee Wan плагин не знает, какие модели человеку видны. Атрибуция в аудите Dee Wan также сохраняется, потому что событие workflow
хранит снимок инсталляции.
Чтобы удалить данные этого плагина для инсталляции, оператор запрашивает это:
curl -X POST -H "Authorization: Bearer local-admin-secret-0123456789" \ http://localhost:8788/admin/installations/<installation id>/delete-dataОн отвечает {"deleted": true}, 404 not_found, если у плагина нет такой инсталляции, и 409
execution_in_progress, пока выполняется расписание или элемент релиза этой инсталляции. Автоматической очистки по сроку
хранения нет. Удаление — это действие, которое кто-то выполняет.
7. Сброс
Заголовок раздела «7. Сброс»rm -rf .wrangler/state # in plugins/scheduling: the local plugin D1npx wrangler d1 migrations apply dee-wan-scheduling --localСторона Dee Wan сбрасывается вместе со своей локальной базой данных. Повторно выполните npm run local:migrate и
npm run local:seed при запущенном local:api.
Что это доказывает, а что нет
Заголовок раздела «Что это доказывает, а что нет»Это доказывает, что два сервиса согласованы: манифест, активация, делегирование запуска, закреплённый условный переход, именованные отказы, повторы, отзыв и удаление.
Этот прогон не проверяет реальные условия развёртывания: DNS, задержки D1, запуск по cron и
работу двух Worker под нагрузкой. Это также не замена собственному набору тестов плагина (npm test в
plugins/scheduling), который намеренно использует тестовое ядро, чтобы причину сбоя можно было искать в одном сервисе,
а не в двух.
npm run test:cross-service в plugins/scheduling выполняет большую часть этого прогона без участия человека: оба
реально запущенных Worker, установка, делегированные запуски, публикация и снятие с публикации по расписанию, потерянный запрос и
потерянный ответ, scheduled_target_stale, workflow_conflict, transition_unavailable,
dependent_content, отмена, изоляция и удаление.
Автоматизированный межсервисный прогон
перечисляет, что он проверяет и что остаётся проверить вручную.
Стабильная версия ещё не выпущена (pre-MVP)
Коммит исходного кода: e207e63239a627fbd2ae9c414ef7c6eb19fcfe5a (с локальными изменениями)
Дата сборки (UTC): 2026-10-21T09:28:24.214Z
Коммит для обновления: не определён: в рабочей копии есть изменения. Примечания к выпуску