Планирование от начала до конца
Оба сервиса на одной машине, без аккаунта Cloudflare и без реальных учётных данных: установите плагин в локальный Dee Wan, запланируйте настоящую публикацию, заставьте раннер выполнить её, а затем заставьте её завершиться сбоем каждым из тех способов, которыми она должна уметь завершаться сбоем.
README плагина — справочник по маршрутам, конфигурации, семантике расписаний и
таблице повторов. Здесь — последовательность действий. Документация на стороне ядра находится в
docs/plugins/ репозитория Dee Wan —
protocol v1, безопасность,
тестирование.
Ничему из описанного ниже не нужны реальный домен, реальный токен или развёрнутый Worker. Не направляйте ничего из этого на продакшен-сайт.
0. Что понадобится
Заголовок раздела «0. Что понадобится»- Node той версии, что закреплена в
.nvmrcрепозитория Dee Wan, и установленные зависимости. - Два свободных порта: 8787 для административного Worker Dee Wan, 8788 для этого плагина. 5173 — для админ-интерфейса.
opensslдля одного случайного ключа.
1. Запуск Dee Wan
Заголовок раздела «1. Запуск Dee Wan»Из корня репозитория Dee Wan:
npm run local:migratenpm run local:seed # prints the sign-in it createsnpm run local:api # admin Worker on http://localhost:8787 (leave running)npm run local:admin # admin UI on http://localhost:5173 (leave running)Перед запуском local:api пропишите три настройки в backend/.dev.vars:
ENVIRONMENT=developmentPLUGIN_DEV_ORIGINS=http://localhost:8788BACKEND_URL=http://localhost:8787Зачем каждая из них:
ENVIRONMENT=development— то, что вообще позволяет URL плагина бытьhttp://.PLUGIN_DEV_ORIGINS— allowlist точных origin, проверяемый для URL манифеста, базового URL, URL активации и URL управления. Никаких подстановочных знаков, никаких упрощений для приватных диапазонов. Без этой записи установка сhttp://localhost:8788отклоняется какurl_not_https.BACKEND_URLотправляется этому плагину какdee_wan_base_url. Без него установка отказывает сbackend_url_unset, а не активирует то, что не сможет обратиться в ответ.
2. Запуск этого плагина
Заголовок раздела «2. Запуск этого плагина»cd plugins/schedulingcp wrangler.example.jsonc wrangler.jsonc # fill in the D1 id it prints belownpx wrangler d1 create dee-wan-schedulingnpx wrangler d1 migrations apply dee-wan-scheduling --local.dev.vars в этом каталоге:
ENVIRONMENT=developmentPUBLIC_ORIGIN=http://localhost:8788DEE_WAN_DEV_ORIGINS=http://localhost:8787ADMIN_SECRET=local-admin-secret-0123456789TOKEN_KEY=<openssl rand -base64 32>DEE_WAN_DEV_ORIGINS — зеркальное отражение PLUGIN_DEV_ORIGINS на стороне этого плагина:
единственный базовый URL Dee Wan без HTTPS, который он примет, и только в режиме разработки.
TOKEN_KEY шифрует хранимые токены инсталляций — потеряете его, и каждую инсталляцию придётся
ротировать.
npx wrangler dev --port 8788 --test-scheduled # leave runningcurl http://localhost:8788/dee-wan/manifest.json # sanity: the manifest names your origin3. Выпуск кода активации
Заголовок раздела «3. Выпуск кода активации»curl -X POST -H "Authorization: Bearer local-admin-secret-0123456789" \ http://localhost:8788/admin/activation-codesОдноразовый, 15 минут, хранится хешированным. Пустое тело выпускает код activate; {"installation_id": "<id>"} выпускает код rotate только для этой инсталляции.
4. Установка в админ-интерфейсе
Заголовок раздела «4. Установка в админ-интерфейсе»Войдите на http://localhost:5173 с аккаунтом, который вывел local:seed, затем
Settings → Plugins → Install:
- URL манифеста
http://localhost:8788/dee-wan/manifest.json; - код активации из шага 3;
- возможности: все четыре, для этого пошагового руководства;
- модели: отметьте
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 — это то, как выглядит хранимый токен. Ни на одной из сторон нет столбца,
строки лога или маршрута, которые показали бы вам сырой токен.
5. Планирование публикации
Заголовок раздела «5. Планирование публикации»В Dee Wan: откройте черновик элемента в этой модели и воспользуйтесь действием Schedule, которое
объявил манифест. Это делегированный запуск: Dee Wan выпускает одноразовый код, ваш браузер попадает
на http://localhost:8788/installations/<id>?dee_wan_launch=…, бэкенд этого плагина обменивает его на
делегирование, устанавливает собственный cookie сессии и перенаправляет на ту же страницу без кода в
URL.
Форма читает элемент через делегирование, поэтому предлагаемые переходы — это те, которые вы можете выполнить прямо сейчас. Выберите локальное время на минуту-две вперёд и часовой пояс. Подтверждение показывает и локальное время с его поясом, и вычисленный момент в 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 — это точная версия, которая будет опубликована, а не «то, что будет текущим в
момент запуска».
6. Запуск выполнения
Заголовок раздела «6. Запуск выполнения»Cron-триггер в wrangler dev сам по себе не срабатывает. С --test-scheduled:
curl "http://localhost:8788/__scheduled?cron=*+*+*+*+*"Один тик захватывает до 20 строк, срок которых наступил, с арендой (lease), отправляет по одной команде на каждую и фиксирует их результат. Затем:
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 — не вашу учётную запись и не
выдуманную.
Сделайте тик до наступления срока — и ничего не будет захвачено; сделайте два тика после — и второй не найдёт работы. Это аренда плюс ключ идемпотентности, и это стоит увидеть хотя бы раз.
7. Намеренные сбои
Заголовок раздела «7. Намеренные сбои»Каждый из этих случаев — реальное состояние, в которое попадёт пользователь. Выполняйте их на черновом сайте.
| Чтобы увидеть | Сделайте это | Ожидайте |
|---|---|---|
scheduled_target_stale |
Запланируйте публикацию, затем отредактируйте и сохраните черновик, затем тик | refused, без повтора, и элемент остаётся неопубликованным |
workflow_conflict |
Запланируйте публикацию, затем опубликуйте элемент вручную, затем тик | refused, без повтора |
dependent_content |
Опубликуйте A, свяжите с ним опубликованный B, запланируйте снятие A с публикации, тик | refused, и Dee Wan называет зависимость |
requester_unauthorized |
Запланируйте от имени пользователя, уберите роль этого пользователя в Dee Wan, тик | refused; полномочия перечитываются при выполнении и никогда не переносятся вперёд |
plugin_unauthorized |
Запланируйте, затем отключите или удалите в Settings → Plugins, тик | refused; расписание сообщает, что плагин потерял доступ |
temporary_failure |
Остановите Worker Dee Wan, тик | retrying с 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, чтобы достигать лимитов за
минуту, а не за четверть часа.
После retry_exhausted, последовавшего за транспортным сбоем, исход действительно неизвестен —
команда могла быть применена экземпляром Dee Wan, который не смог ответить. Проверьте элемент в Dee
Wan. Именно поэтому расписание говорит об этом, а не заявляет о сбое.
8. Ротация и удаление
Заголовок раздела «8. Ротация и удаление»Ротация, которую оператору стоит отработать до того, как она понадобится:
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Затем Settings → Plugins → Rotate с этим кодом. Dee Wan выпускает новый токен, передаёт его сюда и только после этого заменяет свой сохранённый хеш; старый токен остаётся действительным в течение ограниченного перекрытия, а первый запрос с новым токеном завершает перекрытие. Если этот плагин отказывает, старый токен продолжает работать — проверьте и это, выполнив ротацию с уже использованным кодом.
Удаление в Dee Wan отзывает токен немедленно и окончательно. Данные этого плагина остаются: любое
оставшееся расписание при наступлении срока отказывает с plugin_unauthorized — именно это и должен
видеть пользователь, а не молчаливое исчезновение. Атрибуция в аудите Dee Wan тоже остаётся, потому
что событие workflow сохранило снимок инсталляции.
Чтобы удалить данные этого плагина для инсталляции, оператор запрашивает это:
curl -X POST -H "Authorization: Bearer local-admin-secret-0123456789" \ http://localhost:8788/admin/installations/<installation id>/delete-dataОтклоняется с 409 execution_in_progress, пока выполняется расписание этой инсталляции.
Автоматической очистки по сроку хранения нет; удаление — это действие, которое кто-то выполняет.
9. Сброс
Заголовок раздела «9. Сброс»rm -rf .wrangler/state # in plugins/scheduling: the local plugin D1Сторона Dee Wan сбрасывается вместе со своей локальной базой данных; заново выполните
npm run local:migrate и npm run local:seed.
Что это доказывает, а что нет
Заголовок раздела «Что это доказывает, а что нет»Это доказывает, что два сервиса согласованы: манифест, активация, делегирование запуска, закреплённый условный переход, именованные отказы, повторы, отзыв и удаление.
Это ничего не доказывает о развёртывании — реальный DNS, реальная задержка D1, реальное расписание
cron или два Worker под нагрузкой. И это не замена ни одному из наборов тестов: npm test здесь и
npx vitest run test/plugin-*.test.ts в бэкенде Dee Wan — это то, что запускается при каждом
изменении, и оба намеренно используют дублёр для другой стороны, чтобы сбой указывал на один сервис,
а не на два.