Перейти к содержимому
Версия 1.0 ещё не выпущена; проект находится на стадии pre-MVP. См. возможности и ограничения версии 1.0.

Полная проверка с локальным Dee Wan

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

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

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

Следуйте разделу Локальная конфигурация из двух сервисов в «Тестировании плагина», используя этот плагин. Когда закончите:

  • 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.

Войдите по адресу http://localhost:5173 с учётной записью, которую вывел local:seed, затем откройте Настройки → Плагины → Установить:

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

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

Запрошенные плагином права и модели после проверки манифеста, до активации

Установленный плагин Scheduling и предоставленные ему права

Что происходит, по порядку: 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. Именно эта пара сохраняется. С поясом 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 записей, срок которых наступил, с временной блокировкой, отправляет по одной команде на каждую и завершает их. Затем:

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

Откройте Настройки → Плагины, затем Открыть на карточке 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.

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

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

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

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

Окно терминала
rm -rf .wrangler/state # in plugins/scheduling: the local plugin D1
npx 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
Коммит для обновления: не определён: в рабочей копии есть изменения. Примечания к выпуску