การทดสอบปลั๊กอิน
สิ่งที่ปลั๊กอินต้องพิสูจน์ก่อนนำไปใช้กับเนื้อหาจริง วิธีพิสูจน์กับ core ปลอม (fake core) ใน test suite ของคุณเอง วิธีรันทั้งสองเซอร์วิสบนเครื่องเดียว และวิธีทำให้เกิดความล้มเหลวแต่ละแบบโดยตั้งใจ
ดู Protocol v1 สำหรับส่วนของโปรโตคอลที่ต้องทดสอบ และ ความปลอดภัยของปลั๊กอิน สำหรับ invariant ที่กรณีทดสอบด้านล่างมีไว้ปกป้อง
สัญญา (contract)
หัวข้อที่มีชื่อว่า “สัญญา (contract)”ต่อไปนี้คือกรณีที่ปลั๊กอินต้องมี แต่ละกรณีเป็นคุณสมบัติของโปรโตคอล ไม่ใช่ของปลั๊กอินตัวใดตัวหนึ่ง ดังนั้นแต่ละกรณีจึงควรอยู่ใน 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 กับมัน
พิสูจน์กับ core ปลอม
หัวข้อที่มีชื่อว่า “พิสูจน์กับ core ปลอม”อย่าชี้ 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 rootnpm --prefix packages/plugin-conformance run buildcd packages/plugin-conformancenpm pack # writes dee-wan-plugin-conformance-0.1.0.tgzcd /path/to/your-pluginnpm 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ใดก็ได้, headerRetry-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/schedulingnpm test # vitestnpm run typecheckภายใน repository ของ Dee Wan เครื่องมือของมันมาจากการติดตั้งที่ root ของ repository
(node scripts/install-dependencies.mjs ที่ root) และ @dee-wan/plugin-conformance ถูกลิงก์
จาก packages/plugin-conformance: ให้รัน npm run build ที่นั่นก่อน
ตรวจสอบกับ schema ที่เผยแพร่ไว้
หัวข้อที่มีชื่อว่า “ตรวจสอบกับ schema ที่เผยแพร่ไว้”เอกสาร 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 ทำสิ่งนี้
ชุดติดตั้งแบบสองเซอร์วิสบนเครื่อง local
หัวข้อที่มีชื่อว่า “ชุดติดตั้งแบบสองเซอร์วิสบนเครื่อง local”ทั้งสองเซอร์วิสบนเครื่องเดียว โดยไม่ต้องมีบัญชี 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 ปรากฏด้านล่าง
Dee Wan
หัวข้อที่มีชื่อว่า “Dee Wan”ใน 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.mjsnode scripts/install-dependencies.mjs frontendnpm run local:migratenpm run local:api # admin Worker on http://localhost:8787 (leave running)npm run local:seed # needs local:api running; prints the sign-in it createsnpm run local:admin # admin UI on http://localhost:5173 (leave running)ปลั๊กอิน
หัวข้อที่มีชื่อว่า “ปลั๊กอิน”สำหรับปลั๊กอินอ้างอิง จาก root ของ repository:
cd plugins/schedulingcp wrangler.example.jsonc wrangler.jsoncdatabase_id ที่เป็น placeholder ในไฟล์ที่คัดลอกมาใช้ได้: --local ไม่ติดต่อ Cloudflare เลย จึง
ไม่มีขั้นตอน wrangler d1 create ข้าง ๆ ไฟล์นั้น ให้สร้าง .dev.vars:
ENVIRONMENT=developmentPUBLIC_ORIGIN=http://localhost:8788DEE_WAN_DEV_ORIGINS=http://localhost:8787ADMIN_SECRET=local-admin-secret-0123456789TOKEN_KEY=<the output of: openssl rand -base64 32>.dev.varsoverride ค่า production ในwrangler.jsoncสำหรับwrangler devDEE_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 --localnpx wrangler dev --port 8788 --test-scheduled # leave runningcurl 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.mjsnpm run build:packagesnpm --workspace backend run prisma:generatecd plugins/schedulingnpm 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 โดยไม่เปลี่ยนแปลง ยกเว้น headerHostมันสามารถ ทิ้ง 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_SECRETspec ออกรหัส activation ด้วย secret นั้น ติดตั้งผ่านPOST /api/pluginsและเปิดแต่ละหน้าของปลั๊กอินจาก URL ที่มีรหัสของPOST /api/plugins/:id/launchซึ่งเป็นการเรียกเดียวกับที่ปุ่มเปิดปลั๊กอินในส่วนการตั้งค่าและในตัวแก้ไขรายการเรียก
พอร์ตของปลั๊กอินคือ E2E_PLUGIN_PORT (ค่าเริ่มต้น 43190) หากถูกใช้อยู่จะถูกปฏิเสธทันที เช่นเดียวกับ
ทุกพอร์ตที่การรันเปิดให้บริการ ตัวรันดูแลปลั๊กอิน: หากมันตายระหว่างการรันในเบราว์เซอร์ที่ยังทำงานอยู่
การรันจะหยุดและระบุชื่อมัน
Fixture สำหรับความล้มเหลว
หัวข้อที่มีชื่อว่า “Fixture สำหรับความล้มเหลว”วิธีทำให้เกิดแต่ละความล้มเหลวโดยตั้งใจ คอลัมน์ core ปลอมคือสิ่งที่ test ของคุณทำ คอลัมน์ Dee Wan บนเครื่อง local คือวิธีที่สถานะเดียวกันเกิดขึ้นกับ core จริง เมื่อคุณต้องการยืนยันแบบ end to end
Outcome ที่โปรโตคอลกำหนด
หัวข้อที่มีชื่อว่า “Outcome ที่โปรโตคอลกำหนด”ปลั๊กอินทุกตัวจะเจอสิ่งเหล่านี้ ไม่ว่ามันจะทำอะไร แต่ละตัวคือ 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
หัวข้อที่มีชื่อว่า “ความล้มเหลวที่ไม่มี outcome code”สิ่งเหล่านี้ไม่มี 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-conformancebuild และทดสอบใน 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
คอมมิตสำหรับอัปเกรด: ระบุไม่ได้: มีการแก้ไขที่ยังไม่คอมมิต. บันทึกการเปลี่ยนแปลง