ข้ามไปยังเนื้อหา
Dee Wan 1.0 ยังอยู่ระหว่างพัฒนาและยังไม่เปิดตัว (pre-MVP) — ดู ขอบเขตผลิตภัณฑ์ 1.0

การทดสอบปลั๊กอิน

สิ่งที่ปลั๊กอินต้องพิสูจน์ก่อนนำไปใช้กับเนื้อหาจริง วิธีพิสูจน์กับ core ปลอม (fake core) ใน test suite ของคุณเอง วิธีรันทั้งสองเซอร์วิสบนเครื่องเดียว และวิธีทำให้เกิดความล้มเหลวแต่ละแบบโดยตั้งใจ

ดู Protocol v1 สำหรับส่วนของโปรโตคอลที่ต้องทดสอบ และ ความปลอดภัยของปลั๊กอิน สำหรับ invariant ที่กรณีทดสอบด้านล่างมีไว้ปกป้อง

ต่อไปนี้คือกรณีที่ปลั๊กอินต้องมี แต่ละกรณีเป็นคุณสมบัติของโปรโตคอล ไม่ใช่ของปลั๊กอินตัวใดตัวหนึ่ง ดังนั้นแต่ละกรณีจึงควรอยู่ใน suite ของคุณ ไม่ว่าปลั๊กอินของคุณจะทำอะไรก็ตาม

กรณี สิ่งที่ต้องเกิดขึ้น
Activation โค้ดแบบใช้ครั้งเดียวถูกใช้ได้ครั้งเดียวเท่านั้น โค้ดที่ไม่รู้จัก ใช้ไปแล้ว หรือหมดอายุจะถูกปฏิเสธ token ดิบถูกเก็บแบบเข้ารหัสและไม่เคยถูกบันทึกลง log handler ตอบกลับภายในเวลาน้อยกว่า 5 วินาทีมาก และไม่เรียกโปรโตคอลใดด้วยตัวเอง
Rotation kind: rotate แทนที่ token ที่เก็บไว้ token เก่าเลิกถูกใช้งานโดย worker ทุกตัว rotation ที่ถูกปฏิเสธจะคง token เก่าไว้ตามเดิม
Launch โค้ดถูกแลกเปลี่ยนได้ครั้งเดียว ผ่านแบ็กเอนด์ของปลั๊กอิน การแลกเปลี่ยนครั้งที่สองล้มเหลว โค้ดที่ออกให้ installation อื่นล้มเหลว หน้าจัดการส่ง Referrer-Policy: no-referrer และตัด dee_wan_launch ออกจาก URL หลังการแลกเปลี่ยน
Session browser session ของปลั๊กอินเองถูกสร้างตอนแลกเปลี่ยน เป็น cookie ของมันเอง และไม่มี credential ของ Dee Wan อยู่ในนั้น
Pinning การเผยแพร่ pin current_version_id การยกเลิกการเผยแพร่ pin published_version_id ทั้งสองอย่าง pin workflow.revision และ (kind, to) ที่เลือกต้องมาจากการอ่านผ่าน delegation
Idempotency key ได้มาจาก record และ revision ของมัน เหมือนเดิมทุกครั้งที่ retry และเปลี่ยนไปเมื่อ record ถูกตั้งเวลาใหม่
At most once เฉพาะผู้ครอง lease ปัจจุบันเท่านั้นที่เริ่มส่งได้ และ deadline ของคำขอต้องก่อน lease หมดอายุ การกู้คืนอาจส่งคำขอซ้ำด้วย key และ body เดิม core ต้องทำให้เกิดผลไม่เกินหนึ่งครั้งภายในช่วงเก็บ key timeout ไม่ได้ยืนยันว่าคำขอระยะไกลหยุดแล้ว
Replay idempotent_replay ถูกสรุปผลเป็นความสำเร็จ ไม่ใช่ผลกระทบใหม่
การปฏิเสธแบบ terminal invalid_request, plugin_unauthorized, capability_refused, requester_unauthorized, content_not_found, scheduled_target_stale, workflow_conflict, dependent_content, idempotency_key_reused, transition_unavailable และ transition_refused แต่ละตัวถูกสรุปผลโดยไม่มีการ retry ภายใต้ชื่อของตัวเอง
Retries structured rate_limited/429 และ temporary_failure/503 ที่ถูกต้อง, 429/500/502/503/504 ที่ไม่มี outcome และข้อผิดพลาดเครือข่าย/timeout ให้ลองใหม่ภายในเพดานจำนวนครั้งและอายุ เคารพ Retry-After ที่ถูกต้องและไม่เกินเพดาน มิฉะนั้นใช้ backoff พร้อม jitter
Response ที่ไม่น่าเชื่อถือ หากมี outcome แต่รูปแบบผิด รหัสไม่รู้จัก ค่า retry ไม่ตรง หรือ status กับ code ขัดกัน ให้จบเป็นข้อผิดพลาดแม้ status จะลองใหม่ได้ body ที่ไม่มี outcome ให้จบเมื่อไม่ใช่ status สำรองที่ระบุ และไม่ตาม redirect
Isolation ทุกตารางและทุก query ถูกจำกัดขอบเขตตาม installation installation หนึ่งไม่สามารถอ่าน ยกเลิก หรือ execute record ของอีก installation ได้
Revocation หลัง uninstall งานที่ค้างอยู่จะถูกปฏิเสธด้วย plugin_unauthorized และปลั๊กอินรายงานเช่นนั้นแทนที่จะ retry
Secrets ไม่มี test fixture, บรรทัด log, error body หรือภาพหน้าจอใดที่มี token, launch code หรือ activation code
การเขียนแทนบุคคล การเขียนฟิลด์ draft การอัปโหลด media หรือข้อเสนอ ระบุ delegation ของบุคคลที่ทำแทน pin เวอร์ชันหรือ revision ของ draft ที่อ่าน มี Idempotency-Key และถือว่า version_conflict และ draft_conflict เป็นการตัดสินใจของบุคคล ไม่ใช่การ retry

ซอร์สของปลั๊กอิน Scheduling (plugins/scheduling ใน repository ของ Dee Wan) มี suite ที่เป็นตัวอย่างที่ทำจริงของทุกแถว อยู่ใน test/activation.test.ts, launch.test.ts, schedules.test.ts, runner.test.ts, recovery.test.ts, time.test.ts, isolation.test.ts และ flow.test.ts และ test/conformance.test.ts ของมันรันกรณีของโปรโตคอลจาก @dee-wan/plugin-conformance กับมัน

อย่าชี้ suite ของคุณไปที่ Dee Wan ที่กำลังรันอยู่ core ปลอมให้ผลแน่นอน (deterministic) สามารถแทรกความล้มเหลวที่ core จริง ไม่สร้างให้ตามต้องการ และรันเสร็จในระดับมิลลิวินาที

@dee-wan/plugin-conformance คือชุดทดสอบ protocol v1 ที่ติดตั้งได้ ซอร์สอยู่ที่ packages/plugin-conformance ใน repository ของ Dee Wan ซึ่ง build และทดสอบที่นั่น มันยัง ไม่ได้เผยแพร่ไปยัง npm registry การเผยแพร่ต้องได้รับอำนาจ จนกว่าจะเผยแพร่ ปลั๊กอินใน repository อื่น ให้ build จาก checkout ของ Dee Wan แล้วติดตั้ง tarball:

หน้าต่างเทอร์มินัล
node scripts/install-dependencies.mjs # from the Dee Wan checkout root
npm --prefix packages/plugin-conformance run build
cd packages/plugin-conformance
npm pack # writes dee-wan-plugin-conformance-0.1.0.tgz
cd /path/to/your-plugin
npm install --save-dev /path/to/dee-wan-plugin-conformance-0.1.0.tgz

มันมี schema ที่เผยแพร่ไว้อยู่ในตัว จึงไม่อ่านอะไรจาก checkout ของ Dee Wan ขณะรัน มัน export:

Export คืออะไร
createFakeCore() implementation ของ fetch ที่ทำหน้าที่เป็น core ของ Dee Wan สำหรับหนึ่งหรือหลาย installation
runConformance({ describe, it }, subject) ลงทะเบียนกรณีของโปรโตคอลกับ vitest, node:test หรือ jest
conformanceCases(subject) กรณีเดียวกันในรูปฟังก์ชัน async ธรรมดา
validateManifest(value), validateShape(name, value) การ validate กับ manifest schema ที่เผยแพร่และ $defs ของโปรโตคอล
OUTCOMES, CAPABILITIES, TRANSITION_KINDS, PROTOCOL_VERSIONS, INBOUND_RETRY_STATUSES, SECRET_PATTERNS ข้อมูลโปรโตคอลที่เผยแพร่

core ปลอม:

  • ยืนยันตัวตน bearer token ต่อ installation และตอบ plugin_unauthorized ในกรณีอื่น
  • ตอบ /context, /content/:id และ /launch/exchange ด้วย body ตามรูปแบบโปรโตคอล รวมถึง บล็อก workflow และ transition ที่ capability ของ installation และ delegation อนุญาต
  • ใช้ grant ของ capability และ model ของ installation และปฏิเสธ delegation ที่ไม่ครอบคลุมรายการนั้น แบบเดียวกับ core
  • ปฏิเสธ body ของคำสั่งที่ไม่ใช่ transition_command ที่เผยแพร่ด้วย invalid_request
  • ตรวจ pin และ workflow revision ในคำสั่ง และตอบ scheduled_target_stale หรือ workflow_conflict เมื่อไม่ตรงแล้ว และ dependent_content สำหรับรายการที่ทำเครื่องหมายว่ามี dependent
  • เก็บ idempotency record ต่อ key ดังนั้นการ replay แบบตรงทุกอย่างจะตอบ idempotent_replay พร้อม ผลลัพธ์แรกทั้งหมด และ key ที่ใช้ซ้ำกับ input ต่างกันจะตอบ idempotency_key_reused
  • รายงาน redirect ในทุกผลลัพธ์ของการเผยแพร่ จากฟิลด์ redirect ของ item และไม่รายงานในการยกเลิกการเผยแพร่
  • เคลื่อนย้ายภาษาเดียวเมื่อคำสั่งระบุ locale และ installation มี workflow:locale และ แสดงแต่ละภาษาใน workflow.locales สำหรับ installation นั้น
  • ใช้ launch code ได้ครั้งเดียว และปฏิเสธเมื่อใช้กับ installation อื่น
  • ออกซองการเรียกใช้ด้วย createInvocation และใช้รหัสการเรียกใช้ที่ /invocations/exchange ได้ครั้งเดียว เฉพาะกับ installation ที่ออกให้ และก่อนหมดอายุ
  • ตอบ route การเขียน — PUT /content/:id, PUT /drafts, POST /drafts/discard, GET /drafts, POST /media, POST และ GET /content/:id/proposals, GET /proposals/:id — ด้วย shape ที่เผยแพร่ การตรวจ capability, grant และ delegation การ pin เวอร์ชันและ revision ของ draft และ การส่งซ้ำต่อ key ค่าฟิลด์แทนที่เวอร์ชันของ item, draft เป็นของบุคคลที่มอบสิทธิ์ และข้อเสนอไม่เปลี่ยนอะไร
  • pin การเผยแพร่ภาษาเดียวกับเวอร์ชันปัจจุบันของ group และการยกเลิกการเผยแพร่ภาษาเดียวกับ เวอร์ชันที่ live ของภาษานั้นเอง เหมือนที่ core ทำ
  • แทรกความล้มเหลวตามต้องการ — network error, คำตอบที่หายหลังคำสั่งถูก apply แล้ว, status ใดก็ได้, outcome.code ใดก็ได้, header Retry-After หรือ body ที่ผิดรูปโดยตั้งใจ
  • บันทึกคำขอแยกจากผลที่ถูก apply เพื่อให้การกู้คืนส่ง key เดิมซ้ำได้ ขณะที่ผลเกิดไม่เกินหนึ่งครั้ง

มันไม่ตอบรายการ /content แบบแบ่งหน้า

runConformance รับ subject: manifest ของคุณ, สำเนาข้อมูลโปรโตคอลของคุณ, ตัวจำแนกคำตอบของคุณ และ adapter ที่ไม่บังคับสำหรับ activation, การแลก launch code, การส่งคำสั่ง, การเรียกใช้การดำเนินการ (จำเป็นเมื่อ manifest ประกาศ operations) และการเรียกใช้ media source (mediaSource จำเป็นเมื่อประกาศ media_sources) ปลั๊กอินที่ไม่ส่งคำสั่ง เช่น media source ไม่ต้องใส่สำเนาโปรโตคอลและตัวจำแนก แต่ละ adapter เริ่ม instance ใหม่ของปลั๊กอินของคุณที่เชื่อมกับ core ปลอม ไฟล์ vitest ขั้นต่ำ:

import { runConformance } from '@dee-wan/plugin-conformance';
import { describe, it } from 'vitest';
import { subject } from './conformance-subject';
runConformance({ describe, it }, subject);

กรณีที่ตรวจ: ตาราง outcome, status ที่ retry, capability, ชนิดของ transition, เวอร์ชันโปรโตคอล และ รูปแบบของ secret ของคุณเท่ากับที่เผยแพร่; manifest ของคุณผ่านการ validate; ทุก outcome code, status ที่ผิด, retry flag ที่ผิด, outcome ที่ผิดรูป และ status เปล่า ถูกจำแนกตามที่โปรโตคอลกำหนด; activation code ใช้ได้ครั้งเดียว และ rotation code ผูกกับ installation ของมัน; launch code แลกได้ ครั้งเดียว; และผ่านเส้นทางการส่งคำสั่งของคุณเอง คำสั่งที่ pin ไว้ถูกส่งครั้งเดียว, คำตอบที่หายถูกส่งซ้ำ ด้วย key และ body เดิมและ apply ครั้งเดียว, ทุก outcome ที่เป็น terminal จบด้วยชื่อของมันเองและไม่ถูก ส่งอีก, outcome ที่ retry ได้จะ retry และ pin ที่ล้าสมัย, workflow ที่เปลี่ยน, dependent, บุคคลที่เสีย สิทธิ์ และการ uninstall ต่างถูกปฏิเสธ สำหรับการดำเนินการแต่ละอย่างที่ประกาศ ปลั๊กอินตอบหลังแลกรหัส ครั้งเดียวเท่านั้น ด้วยคำตอบที่สคีมาที่เผยแพร่ยอมรับและใช้เฉพาะคอมโพเนนต์ผลลัพธ์ที่ประกาศ และไม่เคยตอบ completed ให้รหัสที่ถูกปฏิเสธ รหัสที่ออกให้ installation อื่น หรือซองที่มีคีย์ไม่รู้จัก สำหรับคอลัมน์ในรายการเนื้อหาแต่ละคอลัมน์ที่ประกาศ การแลกรหัสไม่สร้างการมอบสิทธิ์ และปลั๊กอินตอบบล็อก cells หนึ่งบล็อก เฉพาะแถวที่ถูกถามเท่านั้น แถวละหนึ่งครั้ง และแต่ละเซลล์ถูกวาดตามชนิด cell ที่ประกาศ (url มีเฉพาะที่ลิงก์ และ tone มีเฉพาะที่ป้ายสี) สำหรับ media source แต่ละรายการที่ประกาศ การเรียกดูจะได้คำตอบหลังการแลกรหัส เท่านั้น โดย URL พรีวิวและดาวน์โหลดทุกตัวอยู่บน origin ของ manifest และ URL การส่งมอบทุกตัวอยู่บน origin การส่งมอบที่ประกาศ การประกาศระบุ modes ของมัน และมี delivery_origins เมื่อประกาศ reference เท่านั้น next_cursor ของมันเองนำไปยังหน้าที่ไม่มี รายการซ้ำ รายการที่มันแสดงจะได้คำตอบเป็นรายการนั้น และไม่เคยตอบ completed ให้รหัสที่ถูกปฏิเสธหรือรหัสของ installation อื่น test/conformance.test.ts ของปลั๊กอิน Scheduling คือ subject ที่สมบูรณ์ plugins/media-source-reference/test/conformance.test.ts คือ subject สำหรับ media source

สองสิ่งที่ core ปลอมไม่ใช่: มันไม่ใช่ core และมันใช้ workflow สองสถานะ คือ draft และ published ให้ assert ที่ outcome.code และ HTTP status ห้าม assert ที่ข้อความสำหรับมนุษย์ — ข้อความของ core มีไว้สำหรับคน และไม่คงที่

test/helpers/d1.ts ของปลั๊กอิน Scheduling คือ ชั้นตัวเชื่อมขนาดเล็กบน better-sqlite3 ที่มี API แบบ D1 prepare/bind/first/all/run/batch ทำให้ SQL จริงของปลั๊กอิน — รวมถึงการ claim แบบ compare-and-set และ partial unique index — รันใน suite ได้ มัน apply ทุกไฟล์ใน migrations/ ของ ปลั๊กอิน คัดลอกไปใช้และชี้ไปที่ migration ของคุณเอง

เตรียมลิงก์แพ็กเกจในเครื่องตามขั้นตอนพัฒนา Scheduling แล้วรันชุดทดสอบของปลั๊กอินอ้างอิง:

หน้าต่างเทอร์มินัล
cd plugins/scheduling
npm test # vitest
npm run typecheck

ภายใน repository ของ Dee Wan เครื่องมือของมันมาจากการติดตั้งที่ root ของ repository (node scripts/install-dependencies.mjs ที่ root) และ @dee-wan/plugin-conformance ถูกลิงก์ จาก packages/plugin-conformance: ให้รัน npm run build ที่นั่นก่อน

เอกสาร JSON Schema สองไฟล์ถูกสร้างจาก definition ที่ core ใช้ validate และถูกเผยแพร่พร้อมกับ ทุกรีลีส:

  • schemas/manifest-v1.schema.json — validate manifest ของคุณใน CI ของคุณเอง ก่อนที่ผู้ดูแลจะดึงมันไปใช้
  • schemas/protocol-v1.schema.json — รูปแบบ request และ response ภายใต้ $defs บวกกับตาราง outcomes ที่เครื่องอ่านได้ (HTTP status และ retry flag ต่อ code), capabilities, installation_states และ limits

ตาราง outcomes คือส่วนที่คุ้มค่าจะเชื่อมเข้าไป: ปลั๊กอินที่เก็บสำเนาของตัวเอง — ปลั๊กอินอ้างอิงทำเช่นนั้น ใน src/protocol.ts เพราะมัน import จาก core ไม่ได้ — ควร assert ว่าสำเนาของตนเท่ากับฉบับที่เผยแพร่ เพื่อให้ status หรือ retry flag ที่คลาดเคลื่อนทำให้ build ของคุณล้มเหลว แทนที่จะไปล้มเหลวตอนรันใน production กรณี “protocol copy” ของ runConformance ทำสิ่งนี้

ทั้งสองเซอร์วิสบนเครื่องเดียว โดยไม่ต้องมีบัญชี Cloudflare และไม่ต้องมี credential จริง นี่คือชุดติดตั้ง local ชุดเดียวสำหรับงานปลั๊กอิน Scheduling ตั้งแต่ต้นจนจบ เริ่มต้นจากชุดนี้

คุณต้องมี Node เวอร์ชันที่ .nvmrc ใน repository ของ Dee Wan ตรึงไว้, openssl และพอร์ตเหล่านี้: 8787 (admin Worker ของ Dee Wan), 5173 (admin UI) และ 8788 (ปลั๊กอิน)

พอร์ต 8788 ยังเป็นค่าเริ่มต้นของ npm run local:public ด้วย ซึ่งคือ public read Worker ของ Dee Wan หากคุณ รันตัวนั้นด้วย ให้กำหนดพอร์ตอื่นให้ตัวใดตัวหนึ่ง: npm run local:public -- --port 8789 หรือรัน ปลั๊กอินบนพอร์ตอื่นแล้วใช้พอร์ตนั้นในทุกที่ที่ 8788 ปรากฏด้านล่าง

ใน backend/.dev.vars ใน repository ของ Dee Wan (สร้างขึ้นหากยังไม่มี) ให้เพิ่ม origin ของปลั๊กอิน:

PLUGIN_DEV_ORIGINS=http://localhost:8788

นั่นคือการตั้งค่าเดียวที่คุณเพิ่ม การตั้งค่าของ admin Worker บนเครื่อง local ตั้ง ENVIRONMENT=development ไว้แล้ว ซึ่งเป็นสิ่งที่ทำให้ PLUGIN_DEV_ORIGINS ถูกอ่านได้เลย และ BACKEND_URL=http://localhost:8787 ซึ่งเป็นค่าที่ core ส่งไปให้ปลั๊กอินของคุณเป็น dee_wan_base_url PLUGIN_DEV_ORIGINS คือ allowlist แบบ origin ตรงตัว — ไม่มี wildcard ไม่มีทางลัดสำหรับช่วง private — ที่ถูกตรวจ กับ URL ของ manifest, base, activation และหน้าจัดการ หากไม่มีรายการนี้ การติดตั้งจาก http://localhost:8788 จะถูกปฏิเสธด้วย url_not_https

จากนั้น จาก root ของ repository ให้รันแต่ละคำสั่งที่ทำงานต่อเนื่องใน terminal ของตัวเอง:

หน้าต่างเทอร์มินัล
node scripts/install-dependencies.mjs
node scripts/install-dependencies.mjs frontend
npm run local:migrate
npm run local:api # admin Worker on http://localhost:8787 (leave running)
npm run local:seed # needs local:api running; prints the sign-in it creates
npm run local:admin # admin UI on http://localhost:5173 (leave running)

สำหรับปลั๊กอินอ้างอิง จาก root ของ repository:

หน้าต่างเทอร์มินัล
cd plugins/scheduling
cp wrangler.example.jsonc wrangler.jsonc

database_id ที่เป็น placeholder ในไฟล์ที่คัดลอกมาใช้ได้: --local ไม่ติดต่อ Cloudflare เลย จึง ไม่มีขั้นตอน wrangler d1 create ข้าง ๆ ไฟล์นั้น ให้สร้าง .dev.vars:

ENVIRONMENT=development
PUBLIC_ORIGIN=http://localhost:8788
DEE_WAN_DEV_ORIGINS=http://localhost:8787
ADMIN_SECRET=local-admin-secret-0123456789
TOKEN_KEY=<the output of: openssl rand -base64 32>
  • .dev.vars override ค่า production ใน wrangler.jsonc สำหรับ wrangler dev
  • DEE_WAN_DEV_ORIGINS คือภาพสะท้อนฝั่งปลั๊กอินของ PLUGIN_DEV_ORIGINS: base URL ของ Dee Wan ที่ไม่ใช่ HTTPS เพียงค่าเดียวที่มันยอมรับ และเฉพาะเมื่อ ENVIRONMENT=development เท่านั้น
  • ADMIN_SECRET คือ bearer ของผู้ดูแลระบบ (operator) สำหรับ route /admin/* ของปลั๊กอิน และต้องยาวอย่างน้อย 16 ตัวอักษร
  • TOKEN_KEY คือ base64 ของ 32 ไบต์แบบสุ่ม และใช้เข้ารหัส token ของ installation ที่เก็บไว้ หาก ทำหาย ทุก installation ต้องถูก rotate
หน้าต่างเทอร์มินัล
npx wrangler d1 migrations apply dee-wan-scheduling --local
npx wrangler dev --port 8788 --test-scheduled # leave running
curl http://localhost:8788/dee-wan/manifest.json # the manifest names http://localhost:8788

ออก activation code แบบใช้ครั้งเดียว:

หน้าต่างเทอร์มินัล
curl -X POST -H "Authorization: Bearer local-admin-secret-0123456789" \
http://localhost:8788/admin/activation-codes

มันตอบ 201 พร้อม { "code", "purpose", "installation_id", "expires_at" }: purpose เป็น activate, installation_id เป็น null และ expires_at คือเวลา 15 นาทีข้างหน้า เป็นมิลลิวินาทีนับจาก epoch

ปลั๊กอินของคุณเองต้องมีสามสิ่งเดียวกัน: ให้บริการบน origin ที่คุณอนุญาต ยอมรับ http://localhost:8787 เป็น base URL เฉพาะในโหมด development และมีวิธีออก activation code

ลงชื่อเข้าใช้ admin UI ที่ http://localhost:5173 ด้วยบัญชีที่ local:seed พิมพ์ออกมา จากนั้น การตั้งค่า → ปลั๊กอิน: ตรวจดู http://localhost:8788/dee-wan/manifest.json มอบชุด capability เลือกโมเดล และติดตั้งด้วยโค้ดนั้น เปิดรายการเนื้อหาของโมเดลที่ได้รับ grant แล้วใช้ action ที่ manifest ของคุณประกาศไว้

ทำการติดตั้งผ่าน UI แทนการทำด้วยมือ: route ด้านการดูแลระบบต้องการ session จริง header ของไซต์ และ site:plugins และ UI มีครบทั้งสามอย่างอยู่แล้ว

ข้อควรระวังสองข้อบนเครื่อง local:

  • session cookie แบบ __Host-/Secure ต้องการ HTTPS หรือ localhost ตรงตัว — ไม่ใช่ 127.0.0.1
  • cron trigger จะไม่ทำงานใน wrangler dev เว้นแต่คุณจะสั่ง เมื่อใช้ --test-scheduled คำสั่ง curl "http://localhost:8788/__scheduled?cron=*+*+*+*+*" จะรันหนึ่ง tick

ปลั๊กอินอ้างอิงและ core รันคุยกัน ทั้งสองตัวทำงานจริง ด้วยคำสั่งเดียว ใช้เวอร์ชัน Node ที่ .nvmrc pin ไว้ เตรียม checkout ของ Dee Wan แล้วรันสคริปต์ของปลั๊กอิน:

หน้าต่างเทอร์มินัล
node scripts/install-dependencies.mjs
npm run build:packages
npm --workspace backend run prisma:generate
cd plugins/scheduling
npm run test:cross-service

การทดสอบอยู่ในปลั๊กอิน ที่ test/cross-service/cross-service.test.mjs นอก repository ของ Dee Wan ให้ตั้ง DEE_WAN_CORE_DIR เป็น checkout ของ Dee Wan ที่เตรียมไว้ ไม่ต้องใช้บัญชี Cloudflare ไม่ต้องใช้ credential และไม่ต้องใช้เครือข่ายนอกเหนือจาก 127.0.0.1 ใช้เวลาสองถึงสามนาที เพราะมัน ตั้งเวลาไว้ที่นาทีจริงตามนาฬิกาและรอให้ถึงนาทีนั้น

สิ่งที่มันเริ่ม แต่ละตัวบนพอร์ตว่างที่มันเลือกเอง และหยุดอีกครั้งพร้อมไดเรกทอรีชั่วคราวของมัน:

  • admin Worker ของ Dee Wan ภายใต้ wrangler dev บน D1 local ใหม่ที่ migrate แล้ว โดยมี PLUGIN_DEV_ORIGINS ระบุปลั๊กอิน
  • ปลั๊กอิน Scheduling ภายใต้ wrangler dev --test-scheduled จากสำเนาที่สร้างขึ้นของ wrangler.example.jsonc บน D1 local ใหม่ที่ migrate แล้ว
  • HTTP proxy ภายใน process ของ test BACKEND_URL ของ core ชี้ไปที่มัน ดังนั้นทุกการเรียกจากปลั๊กอินไปยัง core จะผ่านมัน มันส่งต่อ request และ reply โดยไม่เปลี่ยนแปลง ยกเว้น header Host มันสามารถ ทิ้ง request ก่อนส่งต่อ หรือทิ้ง reply หลังจาก core ตอบแล้ว และบันทึกทุกคำสั่ง transition ที่มันเห็น พร็อกซีนี้ทำงานที่ชั้นเครือข่าย ไม่ได้จำลอง core: ทุกคำตอบมาจาก core

ทุกอย่างผ่าน route จริง: bootstrap claim และการลงชื่อเข้าใช้, การเรียก /api/plugins ที่ การตั้งค่า → ปลั๊กอิน ทำ, delegated launch, ฟอร์ม HTML ของปลั๊กอินเอง และ tick ของ /__scheduled มันตรวจสอบ:

กรณี สิ่งที่มันยืนยัน
Activation การติดตั้งผ่าน core จะ activate ปลั๊กอิน การใช้ activation code ซ้ำถูกปฏิเสธด้วย activation_refused
Delegated launch แต่ละ schedule ถูกสร้างผ่าน POST /api/plugins/:id/launch การแลกเปลี่ยนของปลั๊กอิน และฟอร์มของมัน หน้ายืนยันแสดงเวลาท้องถิ่น timezone ของมัน และช่วงเวลา UTC
ก่อนถึงกำหนด tick ก่อนถึงนาทีที่กำหนดไม่ส่งอะไรเลย
Publish, reply หาย core ทำการเผยแพร่ proxy ทิ้ง reply ปลั๊กอิน retry ด้วย key เดิมและ body ที่ตรงกันทุกไบต์ และ core ตอบ idempotent_replay พร้อมข้อมูลผลลัพธ์เดียวกับ reply ที่หาย รวม redirect มี workflow event ของปลั๊กอินหนึ่งรายการ ระบุว่ามาจาก installation และบุคคลที่มอบ delegation
Publish, request หาย proxy ทิ้ง request แรกโดยไม่ส่งต่อ ปลั๊กอิน retry และ core ตอบ transition_applied
Unpublish เวอร์ชันที่เผยแพร่และถูก pin ไว้ถูกยกเลิกการเผยแพร่
scheduled_target_stale draft ถูกแก้ไขหลังจาก pin
workflow_conflict รายการถูกเผยแพร่และยกเลิกการเผยแพร่ด้วยมือหลังจาก pin
transition_unavailable รายการถูกเผยแพร่ด้วยมือหลังจาก pin การเผยแพร่ไว้แล้ว
dependent_content รายการที่เผยแพร่แล้วเชื่อมโยงกับรายการที่กำลังจะถูกยกเลิกการเผยแพร่
Cancellation ยกเลิกใน UI ของปลั๊กอิน core ไม่ถูกเรียกเลย
Isolation session ของ installation หนึ่งไม่สามารถอ่านหรือยกเลิก schedule ของอีก installation ได้
Revocation core uninstall installation ที่มี schedule ค้างอยู่ ปลั๊กอินสรุปผลมันเป็น plugin_unauthorized
สรุปผลแล้วคงเดิม ทุกกรณีที่ถูกปฏิเสธหรือถูกยกเลิกคงสถานะ revision และ publication pointer ของ core ไว้ไม่เปลี่ยนแปลง และ tick ถัดไปไม่ส่งอะไรเลย

CI ของ Dee Wan ไม่รันมัน CI ของปลั๊กอินเอง คือ .github/workflows/ci.yml ในไดเรกทอรีของปลั๊กอิน จะรันมันกับ checkout ของ Dee Wan เมื่อปลั๊กอินมี repository ของตัวเองแล้ว จนกว่าจะถึงตอนนั้น ให้รันด้วยมือเมื่อการเปลี่ยนแปลงแตะฝั่งใดฝั่งหนึ่งของ protocol v1

การรันในเบราว์เซอร์โดยมีปลั๊กอินทำงานจริง

หัวข้อที่มีชื่อว่า “การรันในเบราว์เซอร์โดยมีปลั๊กอินทำงานจริง”

frontend/cypress/e2e/real-scheduling-releases.cy.ts ใช้เบราว์เซอร์ขับหน้า release ของปลั๊กอิน กับ admin Worker จริงและปลั๊กอินจริง ตัวรัน e2e (frontend/scripts/run-e2e.mjs) เริ่มปลั๊กอินสำหรับการรันที่ใช้ E2E_REAL_BACKEND=1 ซึ่งไปถึง spec นี้ได้ คือทั้งชุด หรือ --spec ที่ตรงกับมัน E2E_SCHEDULING_PLUGIN=1 บังคับเปิด และ 0 บังคับปิด

หน้าต่างเทอร์มินัล
E2E_REAL_BACKEND=1 npm --prefix frontend run test:e2e -- --spec cypress/e2e/real-scheduling-releases.cy.ts

สิ่งที่ตัวรันทำ เพื่อให้คุณไม่ต้องทำการตั้งค่าข้างต้นซ้ำด้วยมือ:

  • สร้าง wrangler.jsonc และ .dev.vars ของปลั๊กอินในไดเรกทอรีชั่วคราวที่มันเป็นเจ้าของ จาก wrangler.example.jsonc โดยทำให้ main และ migrations_dir เป็น path สัมบูรณ์และตั้งตัวแปร (PUBLIC_ORIGIN, ENVIRONMENT=development, DEE_WAN_DEV_ORIGINS = admin Worker ของการรันนั้น) ไม่มีสิ่งใด ถูกเขียนใต้ plugins/scheduling/ ไดเรกทอรีนี้ถูกลบเมื่อการรันหยุด และไดเรกทอรีที่การรันซึ่งถูก kill ทิ้งไว้ (socket เจ้าของ /tmp/dwe2e-<suffix>.sock ที่ตั้งชื่อตามไดเรกทอรี ไม่มีใครรอรับการเชื่อมต่ออีกแล้ว) จะถูกลบเมื่อการรันถัดไปเริ่มปลั๊กอิน;
  • apply migration ของปลั๊กอินกับ D1 local ใหม่ในไดเรกทอรีนั้น แล้วเปิดให้บริการด้วย wrangler dev --test-scheduled;
  • เริ่ม admin Worker โดยตั้ง PLUGIN_DEV_ORIGINS ให้ระบุปลั๊กอิน;
  • สร้าง ADMIN_SECRET และ TOKEN_KEY ต่อการรัน และส่ง origin กับ admin secret ให้ spec เป็น CYPRESS_E2E_PLUGIN_ORIGIN และ CYPRESS_E2E_PLUGIN_ADMIN_SECRET spec ออกรหัส activation ด้วย secret นั้น ติดตั้งผ่าน POST /api/plugins และเปิดแต่ละหน้าของปลั๊กอินจาก URL ที่มีรหัสของ POST /api/plugins/:id/launch ซึ่งเป็นการเรียกเดียวกับที่ปุ่มเปิดปลั๊กอินในส่วนการตั้งค่าและในตัวแก้ไขรายการเรียก

พอร์ตของปลั๊กอินคือ E2E_PLUGIN_PORT (ค่าเริ่มต้น 43190) หากถูกใช้อยู่จะถูกปฏิเสธทันที เช่นเดียวกับ ทุกพอร์ตที่การรันเปิดให้บริการ ตัวรันดูแลปลั๊กอิน: หากมันตายระหว่างการรันในเบราว์เซอร์ที่ยังทำงานอยู่ การรันจะหยุดและระบุชื่อมัน

วิธีทำให้เกิดแต่ละความล้มเหลวโดยตั้งใจ คอลัมน์ core ปลอมคือสิ่งที่ test ของคุณทำ คอลัมน์ Dee Wan บนเครื่อง local คือวิธีที่สถานะเดียวกันเกิดขึ้นกับ core จริง เมื่อคุณต้องการยืนยันแบบ end to end

ปลั๊กอินทุกตัวจะเจอสิ่งเหล่านี้ ไม่ว่ามันจะทำอะไร แต่ละตัวคือ outcome.code ที่มีชื่อ พร้อม HTTP status และ retry flag ที่ตายตัว

Outcome กับ core ปลอม กับ Dee Wan บนเครื่อง local
invalid_request ส่ง body key ที่ไม่รู้จัก หรือไม่ใส่ Idempotency-Key เหมือนกัน
plugin_unauthorized ใช้ token ที่ไม่รู้จัก ปิดใช้งานหรือ uninstall installation แล้วส่งคำสั่ง
capability_refused ลบ capability ออกจาก installation จำกัด grant ให้แคบลงใน การตั้งค่า → ปลั๊กอิน
requester_unauthorized ทำเครื่องหมายว่าบุคคลของ delegation ไม่ได้รับอนุญาตอีกต่อไป ลบบทบาท (role) ของบุคคลนั้น หรือปิดใช้งานบัญชี หลังจากทำ delegation แล้ว
content_not_found ขอ instance ที่ไม่รู้จัก ใช้ id จากไซต์อื่น หรือลบโมเดลออกจาก grant
scheduled_target_stale เปลี่ยน current_version_id ของเนื้อหา แก้ไขและบันทึก draft หลังจาก pin มันแล้ว
workflow_conflict เพิ่มค่า revision ของเนื้อหา เผยแพร่แล้วยกเลิกการเผยแพร่รายการด้วยมือหลังจาก pin การเผยแพร่ไว้
dependent_content ตอบ dependent_content ด้วยการแทรก เชื่อมรายการที่เผยแพร่แล้วอีกรายการเข้ากับมัน แล้วลองยกเลิกการเผยแพร่
idempotency_key_reused ใช้ key ซ้ำกับ body ที่ต่างออกไป เหมือนกัน
transition_unavailable ขอ to ที่สถานะนั้นไม่มีให้ เผยแพร่รายการด้วยมือ ขณะที่การเผยแพร่ของรายการนั้นถูก pin ไว้ core ตรวจ transition ก่อน revision
rate_limited แทรก 429 พร้อม Retry-After ทั้งแบบวินาทีและแบบ HTTP date 120 request ในหนึ่งนาทีสำหรับ installation เดียว
temporary_failure แทรกคำตอบ 503 แบบมีโครงสร้าง แล้วปล่อยให้ retry ผ่าน ทำให้เกิดได้ยาก core ยังตอบแบบนี้ขณะที่ผล redirect ของการเผยแพร่ยังไม่ถูกบันทึก การ retry ด้วย key เดิมจะได้ผลลัพธ์ทั้งหมด ให้เชื่อกรณีที่แทรก

สิ่งเหล่านี้ไม่มี outcome.code โปรโตคอลจึงกำหนดไว้เพียงกฎ — อย่าถือว่าเป็นความสำเร็จเด็ดขาด และ อย่า retry ไม่รู้จบ วิธีบันทึกเป็นการตัดสินใจของปลั๊กอินของคุณ คอลัมน์สุดท้ายคือสิ่งที่ ปลั๊กอินอ้างอิงทำ

ความล้มเหลว กับ core ปลอม กับ Dee Wan บนเครื่อง local ปลั๊กอินอ้างอิง
HTTP ชั่วคราวที่ไม่มี outcome แทรก 429, 500, 502, 503 และ 504 โดยไม่มี body ของ outcome ทำให้เกิดได้ยาก ให้เชื่อกรณีที่แทรก ลองใหม่ด้วย backoff ที่มีเพดาน หรือ Retry-After ที่ถูกต้องและมีเพดาน
ความล้มเหลวระดับ transport แทรก network error และ timeout หยุด Dee Wan Worker แล้วปล่อยให้ปลั๊กอินส่งคำสั่ง retry เป็น no_response พร้อม backoff จนถึงเพดาน
response ที่ไม่น่าเชื่อถือ แทรก 200 ที่ outcome.code เป็น code ของ 409, body ที่ไม่ใช่ JSON และ 3xx สร้างขึ้นไม่ได้ นั่นคือเหตุผลที่ต้องแทรก ปฏิเสธเป็น untrusted_response
การกู้คืน lease ทิ้ง record ไว้ในสถานะ running พร้อม lease ที่หมดอายุ kill ปลั๊กอินระหว่างคำสั่ง แล้วปล่อยให้ tick ถัดไป reclaim มัน ส่งใหม่ด้วย idempotency key เดิม ภายในเพดานความล่าช้า

ตรวจรหัสที่จบงานและยืนยันว่าไม่มีคำขอใหม่หลังจบ แยกตรวจการกู้คืนด้วย: เมื่อคำตอบหาย คำขออาจซ้ำด้วย key เดิมและ body ตรงกันทุกไบต์ แต่เกิดผลเพียงครั้งเดียว ให้ผู้ทำงานสองตัวแข่งขันกันและกันผู้ครอง lease เก่าออกก่อนส่ง รายการคำสั่งเพียงอย่างเดียวไม่ได้พิสูจน์ว่าผลเกิดไม่เกินหนึ่งครั้ง

  • แพ็กเกจ contract ยังไม่ได้เผยแพร่ @dee-wan/plugin-conformance build และทดสอบใน repository ของ Dee Wan การเผยแพร่ไปยัง npm ต้องได้รับอำนาจ จนกว่าจะเผยแพร่ ปลั๊กอินใน repository อื่นติดตั้ง tarball ที่สร้างด้วย npm pack
  • การรันข้ามเซอร์วิสครอบคลุมเฉพาะกรณีในตารางของมันเท่านั้น มันไม่ได้ทดสอบ requester_unauthorized, capability_refused, rate_limited, การ rotate token, retry_exhausted, ปลั๊กอิน crash ระหว่างคำสั่ง หรือ cron trigger ที่ทำงานเอง สิ่งเหล่านั้นครอบคลุมในแต่ละฝั่ง กับตัวแทน (double) หรือด้วยมือกับชุดติดตั้งบนเครื่อง local ข้างต้น CI ของ Dee Wan ไม่รันมัน และ CI ของปลั๊กอินเองจะรันก็ต่อเมื่อปลั๊กอินมี repository ของตัวเองแล้ว
  • ไม่มีสิ่งใดในนี้เป็นหลักฐานของการ deploy ทั้งสองเซอร์วิสรันภายใต้ wrangler dev บนเครื่อง local

ยังไม่เปิดตัว (pre-MVP) และยังไม่มีเวอร์ชันเสถียร
คอมมิตซอร์สโค้ด: e207e63239a627fbd2ae9c414ef7c6eb19fcfe5a (มีการแก้ไขที่ยังไม่คอมมิต)
วันที่สร้างเอกสาร (UTC): 2026-10-21T09:28:24.214Z
คอมมิตสำหรับอัปเกรด: ระบุไม่ได้: มีการแก้ไขที่ยังไม่คอมมิต. บันทึกการเปลี่ยนแปลง