Перейти к содержимому
Pre-MVP. Кодовая база 1.0 не является выпущенным продуктом — см. границы продукта 1.0.

Планирование от начала до конца

Оба сервиса на одной машине, без аккаунта Cloudflare и без реальных учётных данных: установите плагин в локальный Dee Wan, запланируйте настоящую публикацию, заставьте раннер выполнить её, а затем заставьте её завершиться сбоем каждым из тех способов, которыми она должна уметь завершаться сбоем.

README плагина — справочник по маршрутам, конфигурации, семантике расписаний и таблице повторов. Здесь — последовательность действий. Документация на стороне ядра находится в docs/plugins/ репозитория Dee Wan — protocol v1, безопасность, тестирование.

Ничему из описанного ниже не нужны реальный домен, реальный токен или развёрнутый Worker. Не направляйте ничего из этого на продакшен-сайт.

  • Node той версии, что закреплена в .nvmrc репозитория Dee Wan, и установленные зависимости.
  • Два свободных порта: 8787 для административного Worker Dee Wan, 8788 для этого плагина. 5173 — для админ-интерфейса.
  • openssl для одного случайного ключа.

Из корня репозитория Dee Wan:

Окно терминала
npm run local:migrate
npm run local:seed # prints the sign-in it creates
npm 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=development
PLUGIN_DEV_ORIGINS=http://localhost:8788
BACKEND_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, а не активирует то, что не сможет обратиться в ответ.
Окно терминала
cd plugins/scheduling
cp wrangler.example.jsonc wrangler.jsonc # fill in the D1 id it prints below
npx wrangler d1 create dee-wan-scheduling
npx wrangler d1 migrations apply dee-wan-scheduling --local

.dev.vars в этом каталоге:

ENVIRONMENT=development
PUBLIC_ORIGIN=http://localhost:8788
DEE_WAN_DEV_ORIGINS=http://localhost:8787
ADMIN_SECRET=local-admin-secret-0123456789
TOKEN_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 running
curl http://localhost:8788/dee-wan/manifest.json # sanity: the manifest names your origin
Окно терминала
curl -X POST -H "Authorization: Bearer local-admin-secret-0123456789" \
http://localhost:8788/admin/activation-codes

Одноразовый, 15 минут, хранится хешированным. Пустое тело выпускает код activate; {"installation_id": "<id>"} выпускает код rotate только для этой инсталляции.

Войдите на http://localhost:5173 с аккаунтом, который вывел local:seed, затем Settings → Plugins → Install:

  • URL манифеста http://localhost:8788/dee-wan/manifest.json;
  • код активации из шага 3;
  • возможности: все четыре, для этого пошагового руководства;
  • модели: отметьте Article (или то, что есть на засеянном сайте).

Используйте UI, а не curl: административные маршруты требуют настоящей сессии, заголовка сайта и права доступа site:plugins, а у UI есть все три.

Settings → Plugins после Review: четыре возможности манифеста и модели сайта, все отмечены, и пустой код активации

Settings → Plugins после Install: Scheduling 0.1.0 в состоянии Active со своим грантом и моделями

Что происходит, по порядку: 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 — это то, как выглядит хранимый токен. Ни на одной из сторон нет столбца, строки лога или маршрута, которые показали бы вам сырой токен.

В 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 — это точная версия, которая будет опубликована, а не «то, что будет текущим в момент запуска».

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 — не вашу учётную запись и не выдуманную.

Сделайте тик до наступления срока — и ничего не будет захвачено; сделайте два тика после — и второй не найдёт работы. Это аренда плюс ключ идемпотентности, и это стоит увидеть хотя бы раз.

Каждый из этих случаев — реальное состояние, в которое попадёт пользователь. Выполняйте их на черновом сайте.

Чтобы увидеть Сделайте это Ожидайте
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. Именно поэтому расписание говорит об этом, а не заявляет о сбое.

Ротация, которую оператору стоит отработать до того, как она понадобится:

Окно терминала
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, пока выполняется расписание этой инсталляции. Автоматической очистки по сроку хранения нет; удаление — это действие, которое кто-то выполняет.

Окно терминала
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 — это то, что запускается при каждом изменении, и оба намеренно используют дублёр для другой стороны, чтобы сбой указывал на один сервис, а не на два.