ข้ามไปยังเนื้อหา
Pre-MVP โค้ดเบส 1.0 ยังไม่ใช่ผลิตภัณฑ์ที่เผยแพร่ — ดู ขอบเขตผลิตภัณฑ์ 1.0

ปลั๊กอิน Scheduling

นี่คือปลั๊กอินแบบ remote service มันตั้งเวลาให้ version ที่ถูก pin หนึ่งตัวถูกเผยแพร่ หรือให้ version ที่เผยแพร่แล้วและถูก pin หนึ่งตัวถูกยกเลิกการเผยแพร่ และดำเนินการเปลี่ยนแปลงนั้นในภายหลังผ่าน plugin protocol v1 ของ Dee Wan มันมี Cloudflare Worker ของตัวเอง, ฐานข้อมูล D1 ของตัวเอง, cron trigger ของตัวเอง และ UI สำหรับจัดการของตัวเอง ไม่มีโค้ดส่วนใดของมันรันอยู่ภายใน Dee Wan

ชื่อ repository, remote และ id ของ manifest ยังเป็นเรื่องที่เจ้าของโปรเจกต์ต้องตัดสินใจ com.example.scheduling เป็นเพียงค่า placeholder ห้ามติดตั้งปลั๊กอินใน production ด้วยค่านี้

เอกสาร: Scheduling ตั้งแต่ต้นจนจบ รันปลั๊กอินกับ Dee Wan ในเครื่อง ฝั่ง core ดู การเขียนปลั๊กอิน, Protocol v1, ความปลอดภัยของปลั๊กอิน และ การทดสอบปลั๊กอิน

ปลั๊กอิน Manifest Dee Wan protocol
0.1.0 v1 v1
Route ใคร ทำอะไร
GET /dee-wan/manifest.json admin ของ Dee Wan Manifest v1 โดยใช้ origin จาก PUBLIC_ORIGIN
POST /admin/activation-codes ผู้ดูแลระบบ (operator) พร้อม Bearer ADMIN_SECRET ออกรหัสใช้ครั้งเดียว มีอายุ 15 นาที body {} ออกรหัส activate; {"installation_id":"..."} ออกรหัส rotate สำหรับ installation (การติดตั้งหนึ่งชุด) นั้นเท่านั้น
POST /dee-wan/activate core ของ Dee Wan activate สร้าง installation และปฏิเสธ installation ที่มีอยู่แล้ว rotate แทนที่ token แต่ละรหัสใช้ได้ครั้งเดียว
GET /installations/:id?dee_wan_launch=dwl_... เบราว์เซอร์ จาก Dee Wan แลกรหัส ตั้ง session และ redirect ด้วย 303 ไปยังหน้าเดิมโดยไม่มีรหัส
GET /installations/:id Session แสดงเนื้อหา ฟอร์มตั้งเวลา และรายการที่ตั้งเวลาไว้
POST /installations/:id/schedules Session และ CSRF token สร้างรายการตั้งเวลา
POST /installations/:id/schedules/:scheduleId/cancel Session และ CSRF token ยกเลิกรายการตั้งเวลาที่อยู่ในสถานะ pending หรือ retrying
POST /admin/installations/:id/delete-data ผู้ดูแลระบบ ลบข้อมูลปลั๊กอินทั้งหมดของ installation นั้น

scheduled() รัน runner หนึ่ง tick

  1. Deploy Worker (ดูด้านล่าง) และตั้งค่า PUBLIC_ORIGIN
  2. สร้างรหัส activation:
    Terminal window
    curl -X POST -H "Authorization: Bearer $ADMIN_SECRET" https://scheduling.example/admin/activation-codes
  3. ใน Dee Wan ไปที่ Settings → Plugins แล้วติดตั้งจาก https://scheduling.example/dee-wan/manifest.json ด้วยรหัสนั้น มอบ capability และโมเดลให้
  4. หากต้องการ rotate token ให้สร้างรหัสด้วย {"installation_id":"<id>"} แล้วส่งให้ action rotate ใน Dee Wan

คัดลอก wrangler.example.jsonc ไปเป็น wrangler.jsonc แล้วใส่ id ของฐานข้อมูล D1

  • Binding: PLUGIN_DB (D1)
  • Cron: * * * * *
  • ตัวแปร: PUBLIC_ORIGIN, ENVIRONMENT, DEE_WAN_DEV_ORIGINS, MAX_ATTEMPTS (5), MAX_LATENESS_MS (900000), LEASE_MS (60000), MAX_RETRY_AFTER_MS (300000)
  • Secret: ADMIN_SECRET (อย่างน้อย 16 ตัวอักษร), TOKEN_KEY (base64 ของ 32 ไบต์แบบสุ่ม; openssl rand -base64 32)
Terminal window
npx wrangler d1 create dee-wan-scheduling
npx wrangler d1 migrations apply dee-wan-scheduling --remote # --local for dev
npx wrangler secret put ADMIN_SECRET
npx wrangler secret put TOKEN_KEY
npx wrangler deploy

การทำ TOKEN_KEY หายจะทำให้ token ของ installation ที่เก็บไว้อ่านไม่ได้ รายการตั้งเวลาจะถูกปฏิเสธด้วย token_unreadable และต้อง rotate ทุก installation

cookie ของ UI สำหรับจัดการใช้ prefix __Host- และ attribute Secure ดังนั้นเบราว์เซอร์ต้องใช้ HTTPS หรือ localhost

  • .dev.vars ของปลั๊กอิน: ENVIRONMENT=development, PUBLIC_ORIGIN=http://localhost:8788, DEE_WAN_DEV_ORIGINS=http://localhost:8787, ADMIN_SECRET=..., TOKEN_KEY=...
  • core ของ Dee Wan: ENVIRONMENT=development, PLUGIN_DEV_ORIGINS=http://localhost:8788, BACKEND_URL=http://localhost:8787
  • รัน npx wrangler d1 migrations apply dee-wan-scheduling --local แล้วรัน npx wrangler dev --port 8788 --test-scheduled
  • หากต้องการกระตุ้น tick ให้รัน curl "http://localhost:8788/__scheduled?cron=*+*+*+*+*"

base URL ของ Dee Wan ที่เป็น HTTP จะถูกยอมรับก็ต่อเมื่อ ENVIRONMENT=development และ origin ของมันอยู่ใน DEE_WAN_DEV_ORIGINS แบบตรงตัวเท่านั้น มิฉะนั้น base URL ต้องใช้ HTTPS และต้องไม่มี credential, ไม่มี query และไม่มี fragment

  • รายการตั้งเวลาสร้างได้จากการ launch เนื้อหาเท่านั้น (action Schedule) ปลั๊กอินอ่านเนื้อหาด้วย delegation และ (kind, to) ที่เลือกต้องอยู่ใน workflow.transitions ที่ได้รับ delegate
  • การ pin: การเผยแพร่ pin current_version_id การยกเลิกการเผยแพร่ pin published_version_id และทั้งสองแบบยัง pin workflow.revision ด้วย รายการตั้งเวลาถูกเก็บโดยผูกกับ instance หลัก; core เผยแพร่ทั้ง translation group
  • มีรายการตั้งเวลาที่ยัง live (pending|retrying|running) ได้ไม่เกินหนึ่งรายการต่อ installation, รายการเนื้อหา และ kind รายการตั้งเวลาใหม่จะแทนที่ (supersede) รายการ live ที่อยู่ในสถานะ pending หรือ retrying ภายใน batch เดียว หากรายการ live กำลัง running การสร้างจะล้มเหลวด้วย execution_in_progress
  • การตั้งเวลาใหม่ (reschedule) จะสร้างแถวใหม่พร้อม revision ใหม่และ idempotency key ใหม่
  • idempotency key คือ sched:<installation_id>:<schedule_id>:r<revision> ค่านี้คงเดิมในทุกการ retry
  • ยกเลิกได้เฉพาะรายการตั้งเวลาที่อยู่ในสถานะ pending หรือ retrying การยกเลิกรายการที่กำลัง running จะคืน execution_in_progress รายการที่เสร็จแล้วยกเลิกไม่ได้ การยกเลิกไม่เคยเรียก Dee Wan และไม่เคยย้อนกลับ transition
  • ในเวลาที่ดำเนินการ core จะตรวจอำนาจของบุคคลนั้น, การ pin และ revision อีกครั้ง
  • การ launch แบบ manage แสดงรายการและยกเลิกรายการตั้งเวลาทั้งหมดของ installation ได้ แต่สร้างรายการตั้งเวลาไม่ได้

สถานะ: pending, retrying, running, completed, refused, cancelled, superseded

แต่ละรายการตั้งเวลาเก็บ due_at แบบ UTC, timezone ของ IANA, เวลาท้องถิ่นตามนาฬิกา และ fold การเปลี่ยนกฎของ timezone จะไม่เขียน due_at ใหม่ การคำนวณใช้เพียง Intl.DateTimeFormat

อินพุต ผลลัพธ์
2026-07-01T09:00 America/New_York 2026-07-01 13:00 UTC
2026-03-29T02:30 Europe/Berlin ปฏิเสธด้วย nonexistent_local_time (นาฬิกากระโดดจาก 02:00→03:00)
2026-10-25T02:30 Europe/Berlin ไม่มี fold ปฏิเสธด้วย ambiguous_local_time
เหมือนกัน, fold earlier 2026-10-25 00:30 UTC (CEST)
เหมือนกัน, fold later 2026-10-25 01:30 UTC (CET)
2026-07-01T09:00 Mars/Base ปฏิเสธด้วย unknown_timezone
2025-12-31T23:59 UTC ที่ผ่านไปแล้ว ปฏิเสธด้วย past_time

หน้ายืนยันและรายการแสดงเวลาท้องถิ่นพร้อม zone และเวลา UTC

cron แต่ละ tick จะหยิบแถวที่ถึงกำหนดได้สูงสุด 20 แถว แบบ round-robin ข้าม installation การ claim คือ compare-and-set บนสถานะและ lease token ที่สังเกตได้ มันตั้งค่า running, lease token ใหม่, lease_until = now + LEASE_MS และ attempt_count + 1 แต่ละ claim ส่งคำสั่งหนึ่งคำสั่ง แถวจะถูก settle ก็ต่อเมื่อ lease token ของ runner เองยังใช้ได้อยู่เท่านั้น แถว running ที่ crash จะถูก reclaim หลังจาก lease หมดอายุ และถูกส่งใหม่ด้วย key เดิม

คำตอบของ Dee Wan ผลลัพธ์ของปลั๊กอิน
200 transition_applied / idempotent_replay completed
400 invalid_request refused ไม่ retry
401 plugin_unauthorized refused ไม่ retry
403 capability_refused / requester_unauthorized refused ไม่ retry
404 content_not_found refused ไม่ retry
409 scheduled_target_stale / workflow_conflict / dependent_content / idempotency_key_reused refused ไม่ retry
422 transition_unavailable / transition_refused refused ไม่ retry
status/code ไม่ตรงกัน, JSON เสีย, 3xx, status อื่น refused untrusted_response
429 retrying ที่ Retry-After (วินาทีหรือ HTTP date)
500/502/503/504, network ล้มเหลว, timeout (10 วินาที) retrying, backoff 15 วินาที·2^(n-1) สูงสุด 5 นาที, jitter 50–100 %
เกิน MAX_ATTEMPTS, เกิน due_at + MAX_LATENESS_MS หรือ Retry-After > MAX_RETRY_AFTER_MS refused retry_exhausted ไม่ส่งอะไรอีก

retry_exhausted หลัง network ล้มเหลวหมายความว่าไม่ทราบผลลัพธ์ ดังนั้นให้ตรวจเนื้อหาใน Dee Wan รายการตั้งเวลาจะไม่ถูกส่งช้ากว่าขีดจำกัดความล่าช้าเลย รวมถึงตอนที่ lease ที่ crash ถูก reclaim ด้วย

ความพยายามแต่ละครั้งถูกบันทึกไว้ใน schedule_attempt (attempt, เวลาเริ่ม, เวลาเสร็จ, code, HTTP status) นี่คือบันทึกเชิงปฏิบัติการสำหรับผู้ใช้ ไม่ใช่ telemetry

  • ข้อมูลที่เก็บ: installation (id, site id, base URL ของ Dee Wan และ token ที่เข้ารหัสด้วย AES-GCM), รหัส activation ที่ hash แล้ว, session ที่ hash แล้ว (30 นาที) พร้อม CSRF token, รายการตั้งเวลา และความพยายามแต่ละครั้ง ไม่มีการเก็บ snapshot ของเนื้อหาและไม่มีการเก็บ session ผู้ใช้ของ Dee Wan
  • รหัส launch และ token ดิบไม่เคยถูก log หรือเก็บในรูปดิบ
  • การถอนการติดตั้งปลั๊กอินใน Dee Wan จะเพิกถอน token ข้อมูลปลั๊กอินยังคงอยู่ รายการตั้งเวลาที่เหลือจะถูกปฏิเสธด้วย plugin_unauthorized เมื่อถึงกำหนด
  • หากต้องการลบข้อมูลปลั๊กอิน ผู้ดูแลระบบเรียก POST /admin/installations/:id/delete-data พร้อม Bearer ADMIN_SECRET การเรียกจะถูกปฏิเสธด้วย 409 execution_in_progress ขณะที่มีรายการตั้งเวลากำลัง running ไม่ได้สร้างกลไกล้างข้อมูลตามระยะเวลาเก็บรักษาแบบอัตโนมัติไว้
Terminal window
npm test # vitest, from this directory
npm run typecheck

test/helpers/d1.ts คือ D1 shim ที่ใช้ better-sqlite3 test/helpers/fake-core.ts จำลอง Dee Wan protocol v1: idempotent replay ต่อ key, การตรวจ pin และ revision, อำนาจที่ได้รับ delegate และการฉีดความล้มเหลว

test/docs.test.ts รันตาราง Runner และ Time ข้างต้นผ่าน classify และ resolveLocalTime ดังนั้นแถวที่ไม่มีใครอัปเดตจะ fail backend/test/plugin-contract.test.ts ของ core ตรวจสำเนาตาราง outcome, capability และรูปแบบของ secret ของปลั๊กอินนี้เทียบกับของ core เอง