* feat(docs): mirror every docs/ page in all 65 locales Extends the documentation mirrors from the 22-page core set (#13940) to every Markdown page under docs/: 152 sources x 65 locales = 9,880 mirrors (6,208 new), language bars rewritten for the full locale list, state adopted so the blocking drift gate now covers all 152 pages. run-translation.mjs: an oversized block made only of table rows or list items (PROVIDER_REFERENCE.md 244-row table, FREE_TIERS.md 71-item list) is cut at item boundaries and rejoined without a blank line — the single 16-40 KB request outlived the backend socket for verbose scripts. 48 older mirrors whose tables had lost rows were retranslated with --force. * docs(i18n): refresh mirrors for the sources the base changed since the branch cut Section-level retranslation of the 29 docs (and README.md) whose source or mirrors moved on release/v3.8.51 during the run, then state adoption; the drift gate is green again on the merged tree.
124 KiB
Radar Free-Model Catalog (ไทย)
🌐 Languages: 🇺🇸 English · 🇪🇹 am · 🇸🇦 ar · 🇦🇿 az · 🇧🇬 bg · 🇧🇩 bn · 🇨🇿 cs · 🇩🇰 da · 🇩🇪 de · 🇬🇷 el · 🇪🇸 es · 🇪🇪 et · 🇮🇷 fa · 🇫🇮 fi · 🇫🇷 fr · 🇮🇪 ga · 🇮🇳 gu · 🇳🇬 ha · 🇮🇱 he · 🇮🇳 hi · 🇭🇷 hr · 🇭🇺 hu · 🇦🇲 hy · 🇮🇩 id · 🇳🇬 ig · 🇮🇹 it · 🇯🇵 ja · 🇬🇪 ka · 🇰🇭 km · 🇮🇳 kn · 🇰🇷 ko · 🇱🇹 lt · 🇱🇻 lv · 🇮🇳 ml · 🇮🇳 mr · 🇲🇾 ms · 🇲🇹 mt · 🇲🇲 my · 🇳🇵 ne · 🇳🇱 nl · 🇳🇴 no · 🇮🇳 or · 🇮🇳 pa · 🇵🇭 phi · 🇵🇱 pl · 🇵🇹 pt · 🇧🇷 pt-BR · 🇷🇴 ro · 🇷🇺 ru · 🇱🇰 si · 🇸🇰 sk · 🇸🇮 sl · 🇷🇸 sr · 🇸🇪 sv · 🇰🇪 sw · 🇮🇳 ta · 🇮🇳 te · 🇹🇷 tr · 🇺🇦 uk-UA · 🇵🇰 ur · 🇺🇿 uz · 🇻🇳 vi · 🇳🇬 yo · 🇨🇳 zh-CN · 🇹🇼 zh-TW
แหล่งข้อมูลหลัก:
src/lib/radar/,src/lib/db/radar.ts,src/app/api/radar/อัปเดตล่าสุด: 2026-09-01 — v3.8.51 ขอบเขตหลักฐานของบริการแบบโฮสต์: กฎฝั่งเซิร์ฟเวอร์ที่อธิบายไว้ที่นี่ได้รับการตรวจสอบเมื่อ 2026-09-01 กับเซิร์ฟเวอร์ Radar ที่ตั้งใจให้เป็นแบบส่วนตัว ณ revision ที่ระบุแน่นอนmain@dce70f004364912f3f144cdb69f4cbcde16093edการติดตั้งใช้งานดังกล่าวไม่ได้เผยแพร่ใน repository OSS นี้ ส่วนความพร้อมใช้งานของบริการแบบโฮสต์ยังคงเป็นสถานะการดำเนินงานที่แยกต่างหาก
Radar เป็น ส่วนเสริมแบบเลือกใช้ ที่ซ้อนทับแค็ตตาล็อกโมเดลฟรีซึ่งมีลายเซ็นกำกับและได้รับการคัดสรรล่าสุด
ลงบนข้อมูลพื้นฐานประจำรุ่น (FREE_MODEL_BUDGETS ใน
open-sse/config/freeModelCatalog.data.ts) ฟังก์ชันนี้มีไว้เนื่องจากภาพรวมของระดับการใช้งานฟรีเปลี่ยนแปลง
เร็วกว่ารอบการออกรุ่น — ผู้ให้บริการอาจเพิ่ม ลด หรือยกเลิกโควตาฟรีในช่วงระหว่างการออกรุ่น
และแค็ตตาล็อกพื้นฐานจะรีเฟรชได้เฉพาะเมื่อมีการเผยแพร่เวอร์ชันใหม่เท่านั้น
ไม่มีสิ่งใดที่ฟรีในวันนี้จะหยุดให้ใช้ฟรีเพราะฟีดระยะไกล Radar จะไม่กำหนดให้รายการพื้นฐาน ต้องชำระเงิน โดยจะเพียงรีเฟรชฟิลด์ขีดจำกัด/สถานะ ณ เวลาอ่าน และสามารถเพิ่ม โมเดลฟรีที่เพิ่งค้นพบระหว่างการออกรุ่นได้ ผู้ดูแลระบบยังคงซ่อน โมเดลไว้เฉพาะที่ได้ และสามารถคืนค่าโมเดลนั้นจากแดชบอร์ดเดียวกัน แค็ตตาล็อกพื้นฐานจะไม่ถูก แก้ไขบนดิสก์ — โปรดดู กฎการผสานโอเวอร์เลย์ ณ เวลาอ่าน ด้านล่าง
สถานะการส่งมอบใน v3.8.51
สถานะต่อไปนี้แยกความแตกต่างระหว่างสิ่งที่ OSS รุ่นนี้รองรับกับสายงาน Radar ในภายหลัง นี่เป็นสถานะระดับโค้ด ไม่ใช่คำมั่นว่าการติดตั้งใช้งานแบบโฮสต์รายการใดรายการหนึ่ง หรือการผสานรวมภายนอกพร้อมใช้งานอยู่ในขณะนี้
| ส่วน | สถานะในรุ่นนี้ |
|---|---|
| ไคลเอนต์แค็ตตาล็อกที่มีลายเซ็น | รองรับแล้วภายใต้ RADAR_ENABLED พร้อมการเลือกเข้าร่วมที่แยกต่างหาก การตรวจสอบ Ed25519 การตั้งค่า/แคชเฉพาะที่แบบเข้ารหัส การแทนค่าการแสดงผล/การเปิดใช้งานที่คงอยู่ tombstone แบบย้อนกลับได้ ตัวจัดกำหนดการ และแดชบอร์ด |
| การเปิดใช้งานสำหรับผู้มีส่วนร่วม | แดชบอร์ดเชื่อมโยงไปยังกระบวนการยืนยันสิทธิ์ผ่าน GitHub ที่โฮสต์บนเซิร์ฟเวอร์ และรองรับคีย์ omr_… ที่มีอยู่แล้ว บริการส่วนตัวเป็นผู้พิจารณาสิทธิ์ของผู้มีส่วนร่วม ส่วนไคลเอนต์ OSS ไม่มีโทเค็น GitHub หรือตรรกะการออกสิทธิ์ |
| การเปิดใช้งานด้วยคีย์ผู้สนับสนุน | รองรับแล้ว คีย์ดิบจะได้รับการตรวจสอบ เข้ารหัสขณะจัดเก็บ ปกปิดเมื่ออ่าน และส่งเฉพาะโดยการซิงค์ฝั่งเซิร์ฟเวอร์ การเปลี่ยนหรือล้างคีย์จะทำให้แคชฟีดที่ขึ้นกับสิทธิ์ทั้งสี่รายการใช้ไม่ได้ |
| ลิงก์แนะนำต่อ | รองรับแล้วในรูปแบบฟีดที่ลงนามแยกต่างหากและรีเฟรชทุกชั่วโมง ลิงก์แบบคงที่พร้อมใช้งานสำหรับระดับชุมชนทันที ส่วนแคมเปญแบบจำกัดยังคงเป็นข้อมูลระดับใช้งานจริง |
| ข้อเสนอสำหรับผู้สนับสนุน | รองรับแล้วในรูปแบบฟีดที่ลงนามแยกต่างหากและใช้เฉพาะในระบบจริง รวมถึงหน้าแดชบอร์ด ไคลเอนต์จะตรวจสอบสคีมาสิทธิประโยชน์แบบปิดอีกครั้ง รักษาแคชที่ใช้ได้ล่าสุด กรองรายการที่หมดอายุ และระบุข้อเสนอของพาร์ทเนอร์อย่างชัดเจน |
| ข้อมูลเชิงลึกและการยกย่องผู้สนับสนุน | รองรับแล้วในรูปแบบฟีดที่ลงนามอย่างเข้มงวดและใช้เฉพาะในระบบจริง พร้อม ELO ที่ Radar เป็นเจ้าของ ข้อมูลข้อเท็จจริงเกี่ยวกับความใหม่และแนวโน้มของแค็ตตาล็อก ป้ายผู้สนับสนุนเฉพาะที่ซึ่งผ่านการยืนยัน หน้าแดชบอร์ด และคำสั่งสถานะ/ซิงค์ CLI แบบเฉพาะที่ |
| การชำระเงินและอีเมลธุรกรรม | ไม่รองรับในไคลเอนต์ OSS การซื้อ การบริจาค การตรวจสอบใบเสร็จ การกู้คืน และการส่งอีเมลเป็นหน้าที่ของบริการส่วนตัว ส่วนความพร้อมใช้งานของบริการแบบโฮสต์ยังคงขึ้นอยู่กับการปรับใช้ภายใต้การกำกับดูแลและการกำหนดค่าผู้ให้บริการ |
| สายงานเอเจนต์วิจัย | ไม่รวมอยู่ในไคลเอนต์รุ่นนี้ เนื้อหาฟีดที่ได้รับการคัดสรรยังคงเป็นข้อมูลฝั่งเซิร์ฟเวอร์ และไม่มีเอเจนต์วิจัยอัตโนมัติทำงานอยู่ในการติดตั้ง OmniRoute |
ตัวอ่านประกาศสาธารณะ
ตัวอ่านประกาศทั่วไปแยกออกจาก feature flag ของ Radar โดยหน้า Home ของแดชบอร์ดและตัวดู
Changelog จะดึง news.json สาธารณะของ repository ผ่าน GET แบบปกติไปยัง
NEWS_JSON_URL (src/shared/utils/releaseNotes.ts) โดยจะไม่ส่งการตั้งค่า Radar, prompt, การกำหนดค่า
provider, บันทึกการใช้งาน หรือสถานะการปิดประกาศภายในเครื่อง
news.json ใช้ schema v2 แบบปิดที่นำไปใช้โดย parseNewsPayload():
schemaVersion: 2และคอลเลกชันitems[]ที่มีขนาดจำกัด;- ค่า
idของประกาศที่คงที่และไม่ซ้ำกัน; - ฟิลด์
activeและpublishedAtในรูปแบบ ISO ที่ระบุไว้อย่างชัดเจน; - เนื้อหาภาษาอังกฤษที่จำเป็น พร้อมเนื้อหาภาษาอื่นที่เป็นตัวเลือก;
- ลิงก์ HTTPS ที่ไม่ต้องใช้ข้อมูลรับรองซึ่งเป็นตัวเลือก และไอคอนที่อยู่ในรายการอนุญาต;
- การเลือกประกาศที่ใช้งานอยู่โดยเรียงจากใหม่ที่สุดก่อน, การใช้ภาษาอังกฤษเป็นภาษาสำรอง และการปิดประกาศภายในเครื่องแยกตาม ID
parser จะยอมรับรูปแบบเอกพจน์เดิม { active, title, message, ... } เป็นการชั่วคราว เพื่อให้
fork รุ่นเก่าสามารถย้ายระบบได้โดยไม่ทำให้มุมมอง Changelog ใช้งานไม่ได้ feed ที่ไม่ถูกต้องจะไม่มีผลใดๆ รายการเปิดตัว Radar
มาพร้อมกับ active: false; การเปลี่ยนเป็น true เป็นการดำเนินการเผยแพร่ที่แยกต่างหากหลัง merge และหลัง deploy
และไม่เปลี่ยน RADAR_ENABLED หรือการเลือกเข้าร่วมการซิงค์ feed ซึ่งแยกเป็นอิสระ
Flag: RADAR_ENABLED (ปิดโดยค่าเริ่มต้น)
Radar ถูกควบคุมแบบครบวงจรด้วย feature flag RADAR_ENABLED
(src/shared/constants/featureFlagDefinitions.ts, หมวดหมู่ policies,
defaultValue: "false")
เมื่อ flag ปิดอยู่ พื้นผิวการใช้งานนี้จะไม่มีอยู่:
- endpoint
/api/radar/*ทั้งหมด รวมถึงการอ่านและเขียนสถานะโมเดลภายในเครื่อง จะส่งคืน404ก่อนเข้าถึงโมดูล Radar ใดๆ - หน้าจอแดชบอร์ด (
/dashboard/radar,/dashboard/radar/setup,/dashboard/radar/combos,/dashboard/radar/offers,/dashboard/radar/intel) จะ rendernotFound() getRadarCatalog()(src/lib/radar/index.ts) จะส่งคืน baseline เดิมที่ไม่มีการแก้ไข — จำนวนรายการเท่าเดิม, ค่าเหมือนเดิม, ทุกรายการมีแท็กorigin: "baseline"— และจะไม่ อ่าน cache ของ feed- จะไม่มีการเรียกเครือข่ายของ Radar เกิดขึ้นเลย; โมดูลซิงค์แต่ละรายการจะส่งคืน
{ status: "disabled" }ก่อนเข้าถึงfetch
นี่คือ gate แบบ superset ที่เข้มงวด: การเปิด flag จะปลดล็อกเฉพาะ หน้าจอ เท่านั้น ไม่มีสิ่งอื่น การเปิด flag จะไม่อัปโหลดข้อมูล, ไม่เริ่มการซิงค์เบื้องหลัง และไม่เปลี่ยน routing หรือการเลือกโมเดล — โปรดดูการเลือกเข้าร่วมที่แยกต่างหากด้านล่าง
การซิงค์ข้อมูลเป็นการเลือกเข้าร่วมที่แยกต่างหาก — คำมั่นด้านความเป็นส่วนตัว
การเปิด RADAR_ENABLED จะปลดล็อกเฉพาะ UI เท่านั้น การซิงค์ feed ต้องมีการเลือกเข้าร่วมครั้งที่สอง
ซึ่งเป็นอิสระจากกันและจัดเก็บไว้ใน radar_settings.opt_in (src/lib/db/radar.ts,
migration 136_radar_cache_settings.sql) syncRadar() จะตรวจสอบทั้ง flag และ การ
เลือกเข้าร่วมก่อนทำการเรียกเครือข่ายใดๆ:
Flag ปิดอยู่ → { status: "disabled" } — ไม่มีการเรียกเครือข่าย
Opt-in เป็น false → { status: "opt_out" } — ไม่มีการเรียกเครือข่าย
เมื่อเปิดใช้งานทั้งสองอย่าง เส้นทางการซิงค์จะเป็นดังนี้:
GET <feed base URL>/v1/catalog/latestพร้อมx-omniroute-radar-schema: 2และ headerAuthorization: Bearer <supporter key>ซึ่งเป็นตัวเลือก (ดูด้านล่าง) โดยค่าเริ่มต้น server จะใช้อาร์ติแฟกต์เปลี่ยนผ่าน v1 ที่ลงนามแยกต่างหากเมื่อไม่มี schema header เพื่อให้ client รุ่นเก่าที่ติดตั้งไว้ยังคง ได้รับการอัปเดต- นี่เป็น flow ของแอปพลิเคชันที่ดาวน์โหลดเพียงอย่างเดียว แต่ยังคงเป็นคำขอ HTTPS โครงสร้างพื้นฐาน ที่โฮสต์อยู่จะได้รับ metadata การเชื่อมต่อตามปกติ เช่น IP ต้นทาง เมื่อมีการกำหนดค่า supporter key การซิงค์จะส่ง key นั้นใน Bearer header ด้วย เพื่อให้บริการสามารถตรวจสอบ สิทธิ์ได้ ณ revision ที่แน่นอนของ private server ซึ่งระบุอยู่ในขอบเขตหลักฐานด้านบน การบันทึกบัญชีคำขอ feed จะใช้ hash ของ key, การใช้งานแบบรวม และ HMAC แบบตัดทอนของ IP ที่หมุนเวียนรายวันสำหรับการตรวจสอบการใช้งานในทางที่ผิดด้วยตนเอง; ตารางเหล่านั้นจะไม่จัดเก็บทั้ง key และ IP ในรูปแบบดิบ บันทึกการเข้าถึงโครงสร้างพื้นฐานและ delivery outbox ที่เข้ารหัสเป็นขอบเขตการปฏิบัติงาน ที่แยกจากกัน
- OmniRoute จะไม่ส่ง prompt, response, การสนทนา, ข้อมูลรับรองของ provider, traffic ของโมเดล, uptime, latency หรือการกำหนดค่า provider ภายในเครื่องไปยังบริการ Radar
- response จะได้รับการตรวจสอบความถูกต้อง, ตรวจสอบโครงสร้าง และจัดเก็บใน cache ภายในเครื่อง (ดู
โมเดลความปลอดภัย) Radar มีเส้นทางเครือข่ายฝั่ง server ทั้งหมดสี่เส้นทาง:
syncRadar()สำหรับ catalog,syncRadarReferrals()สำหรับ referral และsyncRadarOffers()/syncRadarIntel()สำหรับข้อเสนอและ Intel เฉพาะ supporter
supporter key คือ Bearer token ที่เป็นตัวเลือก (radar_settings.supporter_key)
ซึ่งช่วยให้บริการ feed ตัดสินใจว่าจะให้บริการ tier ใด (ดู
Tier) โดย key นี้จะ:
- ถูกจัดเก็บแบบ เข้ารหัสเมื่อพักอยู่ ด้วย helper AES-256-GCM
encrypt()/decrypt()(src/lib/db/encryption.ts) ชุดเดียวกับที่ใช้สำหรับข้อมูลรับรองของ provider - ถูกตั้งค่าผ่าน
POST /api/radar/settings({ supporterKey: "omr_" + 40 hex chars }) และ จะไม่ถูกส่งกลับมาโดยตรง — response จะส่งคืนรูปแบบที่ปิดบังไว้ (omr_****abcd) - เมื่อเปลี่ยนหรือล้าง key ระบบจะทำให้ cache ของ catalog, referral, ข้อเสนอ และ Intel ใช้ไม่ได้แบบ atomic การซิงค์/อ่านครั้งถัดไปจะตรวจสอบสิทธิ์ใหม่ที่ฝั่ง server; การบันทึก key จะไม่ ทำให้เกิดคำขอเครือข่ายหรือใช้ activation key แบบใช้ครั้งเดียวด้วยตัวเอง
- ถูกส่งไปยังบริการ feed ในฐานะ Bearer token ผ่าน sync GET — จะไม่มีข้อมูลอื่นใดเกี่ยวกับ key ออกจาก client
กฎการเข้าถึงและความปลอดภัยที่แสดงก่อนการเลือกเข้าร่วม
แดชบอร์ดที่ยังไม่ได้เปิดใช้งานจะแสดงกฎเหล่านี้จาก
src/app/(dashboard)/dashboard/radar/RadarAccessExplainer.tsx ก่อน การดำเนินการเปิดใช้งานทั้งสองแบบ
ระดับการเข้าถึงมาตรฐานมีดังนี้:
| ระดับ | คุณสมบัติ | การเข้าถึง | กฎการใช้ซ้ำ/การหมดอายุ |
|---|---|---|---|
| ชุมชน | ทุกคน; ไม่ต้องใช้คีย์ | แค็ตตาล็อกฉบับสมบูรณ์ล่าช้าประมาณ 30 วัน | พร้อมใช้งานเสมอ; ไม่มีการออกสิทธิ์ |
| ติดดาว + ติดตาม | GitHub OAuth ตรวจสอบทั้งการติดดาวให้ที่เก็บโค้ดและการติดตามเจ้าของ | อ่านแค็ตตาล็อกแบบสดได้หนึ่งครั้ง จากนั้นกลับเป็นระดับชุมชน | ออกสิทธิ์หนึ่งครั้งต่อการเข้าสู่ระบบ; ไม่มีการออกซ้ำ |
| ผู้มีส่วนร่วม 10 อันดับแรก | อันดับ 1–10 ในการจัดอันดับรายสัปดาห์ฉบับสมบูรณ์ล่าสุด | ใช้งานแบบสด 365 วัน | ขอรับได้ตามต้องการ; การหลุดจากอันดับไม่ทำให้ระยะเวลาที่ได้รับสั้นลง |
| ผู้มีส่วนร่วม 100 อันดับแรก | อันดับ 11–100 ในการจัดอันดับดังกล่าว | ใช้งานแบบสด 90 วัน | ใช้กฎการขอรับตามต้องการ/แบบ idempotent เช่นเดียวกัน |
| การซื้อของผู้สนับสนุน | ซื้อครั้งเดียวสำหรับระยะเวลา 6 เดือน, 1 ปี หรือตลอดชีพ | แค็ตตาล็อกแบบสด ข้อเสนอแบบสดที่มีลายเซ็น และ Intel | ไม่มีการต่ออายุอัตโนมัติ |
| การบริจาค/การให้สิทธิ์ด้วยตนเอง | การบริจาคที่เจ้าของตรวจสอบหรือการให้สิทธิ์โดยเจ้าของเป็นจำนวนวันที่ระบุไว้อย่างชัดเจน/ตลอดชีพ | สิทธิ์แบบสดเดียวกันตลอดระยะเวลาที่ได้รับ | การให้สิทธิ์ที่ตรวจสอบย้อนหลังได้และเป็นแบบ idempotent |
PR ที่ผสานแล้ว, commit และจำนวนบรรทัดที่เปลี่ยนแปลงเป็น เพียงข้อมูลนำเข้าสำหรับการจัดอันดับเท่านั้น การเข้าสู่ระบบจากผู้ที่อยู่นอก 100 อันดับแรกจะ ไม่ได้รับสิทธิ์สำหรับผู้มีส่วนร่วม ไม่ว่าจะมีจำนวน PR เท่าใดก็ตาม การซื้อแบบมีระยะเวลา, การบริจาค, ระยะเวลาสำหรับผู้มีส่วนร่วม และ การให้สิทธิ์ด้วยตนเองจะสะสมต่อจากวันหมดอายุปัจจุบัน; สิทธิ์ตลอดชีพมีลำดับเหนือกว่า การเปลี่ยนแปลงอันดับจะไม่ เพิกถอนย้อนหลังหรือทำให้ระยะเวลาที่มอบให้แล้วสั้นลง
ใบอนุญาตแบบโฮสต์เป็นสิทธิ์ส่วนบุคคล และกฎที่แสดงต่อผู้ใช้คือให้มีการติดตั้งที่ใช้งานอยู่ได้ครั้งละหนึ่งรายการ รุ่นนี้ ไม่ได้ อ้างว่ามีการล็อกกับฮาร์ดแวร์: การซิงค์ OSS จะไม่สร้างลายนิ้วมือฮาร์ดแวร์หรือดูแล สัญญาเช่าอุปกรณ์แบบเข้ารหัส ที่ revision ของเซิร์ฟเวอร์ส่วนตัวซึ่งผ่านการตรวจสอบข้างต้น การบังคับใช้ที่นำไปใช้จริง ประกอบด้วยการตรวจสอบสิทธิ์และสัญญาณให้ตรวจสอบด้วยตนเอง เมื่อพบว่ามีการใช้คีย์แบบสดเดียวกันจาก IP ที่แตกต่างกันเป็นรายการที่สี่ภายใน 24 ชั่วโมง สัญญาณดังกล่าวจะไม่บล็อกหรือเพิกถอนคีย์โดยอัตโนมัติ การกู้คืน จะเพิกถอนและแทนที่คีย์ที่สูญหายโดยคงวันหมดอายุเดิมไว้; การดำเนินการนี้จะไม่เริ่ม ระยะเวลาที่ซื้อหรือได้รับสิทธิ์ใหม่อีกครั้ง
ข้อเสนอแบบสดได้รับการคัดสรรด้วยตนเองและอาจเปลี่ยนแปลงหรือหมดอายุได้ หน้าจอเลือกเข้าร่วมยังระบุ ขอบเขตความเป็นส่วนตัวที่แน่นอนไว้ด้วย: ระบบจะดาวน์โหลดข้อมูลเมตาของแค็ตตาล็อก/การแนะนำที่มีลายเซ็น; คีย์ที่ถูกต้องจะปลดล็อก ข้อเสนอที่มีลายเซ็นและ Intel เพิ่มเติม; คีย์ Bearer และข้อมูลเมตาการเชื่อมต่อตามปกติจะถูกส่งไปยังบริการแบบโฮสต์; พรอมต์, การตอบกลับ, บทสนทนา, ข้อมูลประจำตัวของผู้ให้บริการ, ทราฟฟิกของโมเดล, เวลาทำงาน, เวลาแฝง และการกำหนดค่า ผู้ให้บริการภายในเครื่องจะไม่ถูกส่งไป
การรับคีย์ผู้สนับสนุน
หน้าจอเปิดใช้งาน (/dashboard/radar) เชื่อมโยงไปยังกระบวนการสองแบบสำหรับ การรับ คีย์ผู้สนับสนุน รีโพ OSS เองจะไม่ออกคีย์ ไม่เรียกใช้โค้ดการชำระเงิน และ
ไม่ระบุราคาโดยเด็ดขาด — ราคาจะถูกกำหนดและแสดงทั้งหมดบนหน้าปลายทาง
ไม่ใช่ในรีโพนี้ (การตัดสินใจตามข้อกำหนด D14)
- "ฉันเป็นผู้มีส่วนร่วม" — เปิด
RADAR_CONTRIBUTOR_CLAIM_URL(ค่าเริ่มต้นhttps://radar.omniroute.online/auth/github) ซึ่งเป็นกระบวนการขอรับสิทธิ์ผ่าน GitHub OAuth ที่โฮสต์บน เซิร์ฟเวอร์ Radar แบบส่วนตัว โดยจะตรวจสอบอันดับรายสัปดาห์ล่าสุดที่เสร็จสมบูรณ์: 10 อันดับแรกได้รับสิทธิ์ 365 วัน และอันดับ 11–100 ได้รับสิทธิ์ 90 วัน หากอยู่นอก 100 อันดับแรก จำนวน PR จะไม่ทำให้ได้รับสิทธิ์เข้าถึง กระบวนการ จะตรวจสอบระดับการใช้งานครั้งเดียวจากการกดดาว + ติดตามแยกต่างหากแทน - "สนับสนุนโปรเจกต์" — เปิด
RADAR_SUPPORTER_PLANS_URL(ค่าเริ่มต้นhttps://radar.omniroute.online/planos) ซึ่งเป็นหน้าที่โฮสต์ไว้สำหรับตัวเลือกแบบชำระครั้งเดียว ได้แก่ 6 เดือน, 1 ปี และ ตลอดชีพ หน้า OSS ยังคงไม่แสดงมูลค่าเป็นตัวเงินใดๆ
URL ทั้งสองจะถูกกำหนดค่าฝั่งเซิร์ฟเวอร์ (src/lib/radar/links.ts โดยใช้รูปแบบการเขียนทับด้วย env
แบบเดียวกับ RADAR_FEED_URL) และส่งต่อไปยังแดชบอร์ดผ่านการตอบกลับ
GET /api/radar/settings ที่มีอยู่ (contributorClaimUrl, supporterPlansUrl) — คอมโพเนนต์
ฝั่งไคลเอนต์จะไม่อ่าน process.env ด้วยตัวเอง
| ตัวแปร | วัตถุประสงค์ |
|---|---|
RADAR_CONTRIBUTOR_CLAIM_URL |
เขียนทับ URL สำหรับการขอรับสิทธิ์ของผู้มีส่วนร่วม (ค่าเริ่มต้น https://radar.omniroute.online/auth/github) |
RADAR_SUPPORTER_PLANS_URL |
เขียนทับ URL สำหรับแผนผู้สนับสนุน (ค่าเริ่มต้น https://radar.omniroute.online/planos) |
การกู้คืนคีย์ผู้สนับสนุนที่สูญหาย
จุดเริ่มต้นการกู้คืนของบริการที่โฮสต์ไว้คือ https://radar.omniroute.online/recover และยังมีลิงก์
จากหน้าแผนด้วย การกู้คืนยังคงดำเนินการอยู่นอกไคลเอนต์ OSS ทั้งหมด เนื่องจากการติดตั้งภายในเครื่อง
ไม่เคยได้รับอีเมลของผู้ซื้อ/ผู้มีส่วนร่วม และไม่สามารถสร้างคีย์ดิบขึ้นใหม่จากการตั้งค่าที่เข้ารหัสไว้ได้
- ส่งอีเมลที่เชื่อมโยงกับคีย์ บริการจะแสดงหน้าตอบรับแบบเดียวกัน ไม่ว่าจะมี ไลเซนส์ที่กู้คืนได้หรือไม่ก็ตาม เพื่อไม่ให้แบบฟอร์มเปิดเผยว่าบัญชีใดมีอยู่
- หากมีสิทธิ์ ระบบส่งมอบจะส่งลิงก์อายุสั้นที่ใช้ได้ครั้งเดียว เมื่อเปิดลิงก์ ระบบจะย้าย
โทเค็นไปยังคุกกี้
HttpOnly/Secureชั่วคราวที่เข้ารหัสทันที และเปลี่ยนเส้นทางไปยัง URL/recoverที่ไม่มีพารามิเตอร์ หน้าดังกล่าวจะไม่มีโทเค็น อีเมล คีย์เก่า หรือคีย์ทดแทน - ยืนยันการเพิกถอน บริการส่วนตัวจะเพิกถอนคีย์เดิม สร้างคีย์ทดแทนโดยใช้ แผน/วันหมดอายุเดิม และจัดเข้าคิวสำหรับส่งทางอีเมลภายในธุรกรรมเดียวกัน ระบบจะไม่ ส่งคีย์ทดแทนกลับไปยังเบราว์เซอร์
- วางคีย์ทดแทนลงใน
/dashboard/radarตอนนี้คีย์เก่าต้องลดระดับเป็นcommunityส่วน คีย์ทดแทนต้องทำให้เกิดการซิงค์liveที่ผ่านการยืนยัน การเปิดลิงก์กู้คืนเดิมซ้ำต้องล้มเหลว พร้อมการตอบกลับแบบทั่วไปว่าลิงก์ไม่ถูกต้อง/หมดอายุ
เส้นทางการกู้คืนและระบบส่งอีเมลที่โฮสต์ไว้อาจมีอยู่ในโค้ด แต่ยังไม่พร้อมใช้งานในการปรับใช้บางชุด อย่าระบุว่ากระบวนการนี้พร้อมใช้งานจริงจนกว่าจะปรับใช้เซิร์ฟเวอร์ กำหนดค่าผู้ให้บริการจัดส่ง ด้วยผู้รับที่ควบคุมได้ และทดสอบลิงก์แบบใช้ครั้งเดียวครบทั้งกระบวนการแล้ว
เมื่อผู้เยี่ยมชมมีคีย์แล้ว (omr_ + อักขระเลขฐานสิบหก 40 ตัว) หน้าจอเปิดใช้งาน
(src/app/(dashboard)/dashboard/radar/page.tsx) จะมีช่องสำหรับวางคีย์เป็นเส้นทางหลัก:
การวางคีย์และส่งแบบฟอร์มจะส่ง POST /api/radar/settings
({ optIn: true, supporterKey }) ในการเรียกครั้งเดียว — การวางคีย์จะทั้งตั้งค่าคีย์และเลือกเข้าร่วม
เพื่อปลดล็อกหน้าจอ รูปแบบ (omr_ + อักขระเลขฐานสิบหก 40 ตัว) จะได้รับการตรวจสอบที่ฝั่งไคลเอนต์ก่อน
ด้วยตัวช่วย isValidSupporterKeyFormat() ที่ใช้ร่วมกัน (src/lib/radar/supporterKey.ts)
เพื่อช่วยให้ประสบการณ์ผู้ใช้ดีขึ้น แต่ไม่ว่าอย่างไรสคีมา Zod ของเซิร์ฟเวอร์คือการตรวจสอบที่มีผลชี้ขาด เมื่อ
ตั้งค่าคีย์แล้ว หน้าจอเปิดใช้งานจะแสดงรูปแบบที่ปกปิดไว้ (supporterKeyMasked จาก
GET /api/radar/settings) แทนช่องป้อนข้อมูลว่าง พร้อมส่วนควบคุม "เปลี่ยนคีย์" เพื่อ
วางคีย์ใหม่ — ระบบจะไม่แสดงคีย์ดิบอีก ปุ่มขอรับสิทธิ์/แผนสองปุ่มด้านบน
ยังคงเป็นวิธีสำหรับ รับ คีย์ตั้งแต่แรก ส่วนช่องป้อนข้อมูลนี้คือจุดที่ผู้ปฏิบัติงาน
ซึ่งมีคีย์อยู่แล้วใช้เปิดใช้งานคีย์นั้น
การเปิดใช้งานตั้งแต่ต้นจนจบและการตั้งค่าแบบมีคำแนะนำ
บริการฟีดส่วนตัวและไคลเอนต์ OSS นี้มีขอบเขตที่จำกัดไว้อย่างตั้งใจ: บริการ จะออกและตรวจสอบคีย์ผู้สนับสนุน ขณะที่การติดตั้ง OmniRoute ภายในเครื่องจะเข้ารหัสคีย์ ซิงค์อาร์ติแฟกต์ที่ลงนามแล้วจากฝั่งเซิร์ฟเวอร์ และแนะนำการตั้งค่าผู้ให้บริการ ลำดับการตรวจสอบแบบมีผู้ช่วยคือ:
- รับคีย์ที่ออกใหม่หรือกู้คืนจากขั้นตอน contributor claim, plans/checkout, กระบวนการกู้คืน หรือผู้ดูแลเซิร์ฟเวอร์ส่วนตัวที่ได้รับอนุญาต อย่าวางคีย์ดิบลงในบันทึก ภาพหน้าจอ ความคิดเห็นใน issue หรืออาร์กิวเมนต์บรรทัดคำสั่ง
- เปิดใช้งาน feature flag
RADAR_ENABLEDบนการติดตั้ง OmniRoute ภายในเครื่อง การดำเนินการนี้จะแสดง UI แต่จะยังไม่มีการสื่อสารผ่านเครือข่ายจนกว่าจะบันทึกการยินยอมเข้าร่วมแยกต่างหาก - เปิด
/dashboard/radarวางคีย์ แล้วเปิดใช้งาน เบราว์เซอร์จะส่งคำขอภายในเครื่องหนึ่งครั้งไปยังPOST /api/radar/settingsพร้อม{ optIn: true, supporterKey }; คีย์จะถูกเข้ารหัสภายในเครื่อง และ การตอบกลับจะมีเพียงomr_****<last4> - ปล่อยให้หน้าจอเปิดใช้งานดำเนินการซิงค์แค็ตตาล็อก หรือเลือก ซิงค์ตอนนี้ ยืนยันว่าหน้า
แสดงสถานะ
liveเวอร์ชันฟีด และเวลาที่ดึงข้อมูล สำหรับการวินิจฉัยภายในเครื่องที่ผ่านการตรวจสอบสิทธิ์GET /api/radar/statusจะรายงานสถานะการยินยอมเข้าร่วม/การมีอยู่ของคีย์ และสถานะแคชทั้งสี่ โดยไม่ส่งคืน คีย์POST /api/radar/sync-allสามารถรีเฟรชแค็ตตาล็อก การแนะนำต่อ ข้อเสนอ และ Intel ได้อย่างชัดเจน - เปิด
/dashboard/radar/setup?provider=<provider>ไปยัง URL สำหรับข้อมูลรับรองที่ผู้ให้บริการเป็นผู้ดูแล เลือก เพิ่ม API key บันทึกผ่านแบบฟอร์มจริงของผู้ให้บริการ กลับไปยังคู่มือ แล้วเรียกใช้ ทดสอบการเชื่อมต่อ คู่มือใช้เส้นทางปกติ/api/providersและ/api/providers/<connection-id>/test; โดยจะไม่สร้างข้อมูลรับรอง Radar แยกต่างหาก - เปิด
/dashboard/radar/combosหลังจากมีการเชื่อมต่อผู้ให้บริการที่เข้ากันได้อย่างน้อยสองรายการทำงานอยู่ ตรวจสอบกลุ่มที่แนะนำและสร้าง combo ผ่าน combo API ที่มีอยู่ ข้อเสนอและ Intel ยังคงเป็นแคชแบบลงนามที่ใช้ได้เฉพาะliveซึ่งแยกจากกัน และสามารถตรวจสอบได้ในหน้า Radar เฉพาะของแต่ละรายการ - โหลด
/dashboard/radarและหน้าตั้งค่าใหม่ การยินยอมเข้าร่วม สถานะคีย์ที่ถูกปกปิด แคชที่ผ่านการตรวจสอบ การเชื่อมต่อ ผู้ให้บริการที่บันทึกไว้ และการดำเนินการทดสอบต้องยังคงอยู่หลังการโหลดใหม่ บันทึกหลักฐานเฉพาะหลังจาก คีย์ดิบและข้อมูลรับรองของผู้ให้บริการไม่ปรากฏให้เห็นแล้วเท่านั้น
การบันทึกคีย์เพียงอย่างเดียวไม่ใช่หลักฐานยืนยันสิทธิ์ live หลักฐานคือการทำงานร่วมกันของผลลัพธ์
GET /v1/license/check จากบริการส่วนตัว tier live ที่แค็ตตาล็อก OSS ให้บริการ แคชที่ลงนามและผ่านการตรวจสอบ
และกระบวนการเชื่อมต่อ/ทดสอบผู้ให้บริการจริง คีย์ที่ไม่ถูกต้อง หมดอายุ หรือถูกเพิกถอนจะลดระดับ
แค็ตตาล็อกเป็น community อย่างปลอดภัย และต้องไม่ถูกรายงานว่าเป็นการตรวจสอบคีย์ live ที่สำเร็จ
ลิงก์แผงผู้ดูแลส่วนตัว
RADAR_ADMIN_URL สามารถเพิ่ม ผู้ดูแล Radar ↗ ต่อจากรายการ Radar สำหรับผู้ใช้
ในส่วน Costs ของแถบด้านข้างได้ทันที โดยจงใจไม่มีค่าเริ่มต้น: เมื่อตัวแปรนี้
ไม่ได้ตั้งค่าหรือไม่ถูกต้อง แถบด้านข้างแบบคงที่ command palette และหน้าจอปรับแต่งแถบด้านข้างจะไม่มี
รายการผู้ดูแลและไม่มี URL ส่วนตัว
ค่าจะถูกประมวลผลฝั่งเซิร์ฟเวอร์และส่งต่อผ่านการตอบกลับ
GET /api/settings ที่ผ่านการตรวจสอบสิทธิ์สำหรับการจัดการ ไปยังเซสชันแดชบอร์ดที่ผ่านการตรวจสอบสิทธิ์เท่านั้น หรือไปยัง
เจ้าของ loopback ที่เชื่อถือได้ระหว่างการ bootstrap ภายในเครื่องแบบไม่ต้องเข้าสู่ระบบ การตรวจสอบสิทธิ์ด้วย CLI, บริการภายใน และ API key
ขอบเขต manage จะไม่ได้รับค่านี้ เบราว์เซอร์จะตรวจสอบการตอบกลับอีกครั้งก่อนสร้าง
ลิงก์ภายนอก ซึ่งจะเปิดด้วย noopener noreferrer
ใช้ URL ของ HTTPS tunnel/tailnet ที่ไม่มีข้อมูลรับรอง ระบบยอมรับ HTTP แบบไม่เข้ารหัสเฉพาะสำหรับ SSH
forward แบบ loopback เช่น http://127.0.0.1:9351; scheme อื่น ข้อมูลรับรองที่ฝังอยู่ URL ที่มีรูปแบบไม่ถูกต้อง และ
ปลายทาง HTTP ระยะไกลจะถูกปฏิเสธโดยค่าเริ่มต้นและทำให้การนำทางไม่ทำงาน
โมเดลความปลอดภัย
ลายเซ็น Ed25519 บนไบต์ที่ตรงกันทุกประการ
เพย์โหลดฟีดได้รับการลงลายเซ็นด้วย Ed25519 โดย verifyFeedBytes()
(src/lib/radar/verify.ts) จะตรวจสอบลายเซ็นบน ไบต์ของการตอบกลับที่ตรงกันทุกประการ
ซึ่งได้รับผ่านเครือข่าย — เพย์โหลดจะไม่ถูกทำให้เป็นอนุกรมใหม่ก่อนการตรวจสอบ ดังนั้น
การเข้ารหัสใหม่แบบไบต์ต่อไบต์จึงไม่สามารถทำให้การตรวจสอบลายเซ็นใช้ไม่ได้หรือถูกข้ามไปอย่างเงียบ ๆ
หากการตรวจสอบล้มเหลว (invalid_signature) การซิงค์จะถูกยกเลิกก่อนที่เพย์โหลดจะถูก
แยกวิเคราะห์หรือแคช
พินคีย์สาธารณะ + การหมุนเวียนคีย์
คีย์สาธารณะที่ใช้ตรวจสอบถูกพินไว้ใน src/lib/radar/pinnedKeys.ts
(PINNED_FEED_PUBLIC_KEYS) ซึ่งเป็นอาร์เรย์เพื่อให้สามารถเพิ่มคีย์ใหม่ไว้ด้านหน้า
ก่อนการหมุนเวียนคีย์ ขณะที่ฟีดเก่าในแคชซึ่งลงลายเซ็นด้วยคีย์ก่อนหน้ายังคงใช้ได้จนกว่า
จะมีการซิงค์ใหม่
การเขียนทับด้วยตัวแปรสภาพแวดล้อมที่เป็นมิตรต่อการฟอร์ก
ตัวแปรสภาพแวดล้อมสองตัวช่วยให้ฟอร์กและผู้โฮสต์ระบบด้วยตนเองสามารถกำหนดให้ไคลเอนต์ใช้ฟีดของตนเองแทน บริการ OmniRoute เริ่มต้น — ดู วิธีโฮสต์ฟีดด้วยตนเอง ด้านล่าง:
| ตัวแปร | วัตถุประสงค์ |
|---|---|
RADAR_FEED_URL |
เขียนทับ URL ฐานของฟีด (ค่าเริ่มต้น https://radar.omniroute.online) |
RADAR_FEED_PUBKEY |
เขียนทับคีย์สาธารณะที่พินไว้ (SPKI แบบ base64-DER หรือ PEM) โดยแทนที่อาร์เรย์ในตัวด้วยคีย์เดียวนี้ |
ขีดจำกัดล่างของเวอร์ชัน
syncRadar() จะปฏิเสธฟีดที่ดาวน์โหลดมาหาก version ไม่ใหม่กว่าเวอร์ชันที่แคชอยู่ในปัจจุบันอย่างเคร่งครัด
(compareVersions() ซึ่งเปรียบเทียบ YYYY.MM.DD.n แบบคั่นด้วยจุด) —
{ status: "stale" } วิธีนี้ป้องกันไม่ให้ปลายทางฟีดที่ถูกเจาะระบบหรือกำหนดค่าไม่ถูกต้อง
ย้อนเวอร์ชันไคลเอนต์กลับไปใช้เพย์โหลดที่เก่ากว่าและลงลายเซ็นต่างกัน
วันที่สองค่า และเหตุผลที่เก็บไว้ทั้งคู่
ฟีดที่แคชไว้มีวันที่แยกกันสองค่า และการป้องกันความสับสนระหว่างสองค่านี้คือเหตุผลหลัก ที่ต้องเก็บไว้ทั้งคู่:
| ฟิลด์ | มาจาก | ตอบคำถาม |
|---|---|---|
generatedAt |
เนื้อหาฟีดที่ลงลายเซ็น | ข้อมูล มีอายุเท่าใด |
fetchedAt |
นาฬิกาของการติดตั้งนี้ | การติดตั้งนี้ ดาวน์โหลด ข้อมูลเมื่อใด |
ฟีดที่เพิ่งดึงมาเมื่อไม่กี่นาทีก่อนอาจมีตัวเลขที่เก่าหลายสัปดาห์ ดังนั้น fetchedAt เพียงอย่างเดียว
จึงไม่สามารถบอกผู้ดูแลได้ว่าโอเวอร์เลย์ใหม่กว่าข้อมูลพื้นฐานที่รองรับอยู่หรือไม่ ทั้งสองค่าจะถูก
จัดเก็บถาวรใน radar_feed_cache ส่งคืนโดย getRadarCatalog().meta และรายงาน
แยกกันโดย GET /api/radar/status แถวที่ถูกแคชก่อนมีคอลัมน์ generated_at
(ไมเกรชัน 163) จะถูกอ่านกลับมาเป็น null — ค่าที่ไม่ทราบจะยังคงเป็นค่าที่ไม่ทราบ แทนที่จะ
นำเวลาดึงข้อมูลมาใช้แทน radar_referrals_cache เก็บ generated_at ของตนเองมาตั้งแต่
ไมเกรชัน 142
ขีดจำกัดล่างของเวอร์ชันข้างต้นจะเปรียบเทียบ version ไม่ใช่วันที่ค่าใดค่าหนึ่ง
ยังมีช่องว่างสองจุด และทั้งสองจุดเป็นการจงใจ: แดชบอร์ดยังคงแสดงเฉพาะ Last fetched ดังนั้นการอ่าน
วันที่บิลด์ที่นั่นจึงต้องมีป้ายกำกับใหม่ (รวมถึงรายการภาษาท้องถิ่นทั้ง 41 รายการ); และแคชข้อเสนอกับข้อมูล
เชิงลึกไม่ได้เก็บวันที่บิลด์เลย แม้ว่าสคีมาฟีดของแคชเหล่านั้นจะมีวันที่ดังกล่าว — ดังนั้น GET /api/radar/status จึงละเว้นฟิลด์นี้สำหรับแคชทั้งสองแทนที่จะรายงาน null
ซึ่งอาจถูกตีความว่า "ไม่ทราบ"
การตรวจสอบความถูกต้องของสคีมา
ไบต์ที่ดาวน์โหลดมาจะถูกแยกวิเคราะห์และตรวจสอบกับ RadarFeedSchema
(src/lib/radar/feedSchema.ts ซึ่งเป็นสคีมา Zod) หลังจาก ตรวจสอบลายเซ็นแล้ว
หากสคีมาไม่ตรงกัน ระบบจะส่งคืน { status: "invalid_schema" } และไม่แก้ไข
แคช เพย์โหลดที่แคชไว้จะถูกตรวจสอบความถูกต้องซ้ำเชิงป้องกันทุกครั้งที่อ่าน
(getRadarCatalog()) — แถวแคชที่เสียหายหรือถูกแก้ไขด้วยมือจะย้อนกลับไปใช้
ข้อมูลพื้นฐานแทนที่จะถูกนำไปให้บริการ
ขีดจำกัดขนาดการตอบกลับ (10 MB)
syncRadar() บังคับใช้ ขีดจำกัดตายตัว 10 MB กับเนื้อหาการตอบกลับของฟีด — ฟีดที่ลงลายเซ็น
เป็นเอกสาร JSON ขนาดระดับ KB ดังนั้นข้อมูลใด ๆ ที่เกินขนาดนี้บ่งชี้ถึง RADAR_FEED_URL
ที่กำหนดค่าไม่ถูกต้องหรือเป็นอันตราย (หรือระบบต้นทางที่ส่งข้อมูลขยะ) ไม่ใช่แค็ตตาล็อกที่ถูกต้อง
การบังคับใช้มีสองชั้น:
- การตรวจสอบ
Content-Lengthล่วงหน้าจะข้ามการอ่านเนื้อหาทั้งหมดเมื่อ ส่วนหัวประกาศค่าที่เกินขีดจำกัดอยู่แล้ว - การตรวจสอบผลรวมสะสมขณะอ่านเนื้อหาจะบังคับใช้ขีดจำกัดแม้ว่า
Content-Lengthจะไม่มีอยู่หรือระบุขนาดจริงต่ำกว่าความเป็นจริง — ระบบจะไม่ เชื่อถือส่วนหัวเพียงอย่างเดียว การต่อชิ้นส่วนที่สะสมไว้เข้าด้วยกันจะรักษาไบต์ที่ตรงกันทุกประการ ซึ่งจำเป็นสำหรับการตรวจสอบลายเซ็น Ed25519 ในภายหลัง
เมื่อเกินขีดจำกัด ระบบจะส่งคืน { status: "too_large" } และไม่แก้ไขแคช
โดยเป็นไปตามรูปแบบที่ไม่ทำลายข้อมูลเช่นเดียวกับความล้มเหลวในการซิงค์อื่น ๆ ทุกกรณี
(invalid_signature, invalid_schema, stale)
ระดับ: community และ live
สคีมาของฟีดมีฟิลด์ tier: "community" | "live" ซึ่งบริการฟีดเป็นผู้กำหนด ที่ฝั่งเซิร์ฟเวอร์
ตามคำขอ (โดยพิจารณาว่ามีคีย์ผู้สนับสนุนหรือไม่และคีย์นั้นถูกต้องหรือไม่)
— ไคลเอนต์จะไม่เป็นผู้กำหนดระดับของตนเอง
community— แคตตาล็อกฟรีที่ล่าช้ากว่าข้อมูลล่าสุดประมาณ 30 วัน คำขอที่ไม่ได้ยืนยันตัวตนหรือใช้คีย์ที่ไม่ถูกต้องจะได้รับระดับนี้live— แคตตาล็อกล่าสุด ซึ่งให้บริการแก่คำขอที่มีคีย์ผู้สนับสนุนที่ถูกต้อง
คีย์ผู้สนับสนุนที่ไม่ถูกต้องหรือหมดอายุจะถูกลดระดับเป็น community — โดยจะไม่ถือเป็น
ข้อผิดพลาด เส้นทางการซิงค์จะแยกเฉพาะความล้มเหลวด้านลายเซ็น/สคีมา/เวอร์ชัน (ทั้งหมด
กู้คืนได้และไม่ส่งผลร้ายแรงต่อสถานะที่แคชไว้) ออกจากผลสำเร็จ { status: "updated", version, tier } ไคลเอนต์ไม่จำเป็นต้องจัดการเส้นทางข้อผิดพลาดเฉพาะระดับใดๆ
ระดับที่ให้บริการมาจากส่วนหัวการตอบกลับ ไม่ใช่เนื้อหาที่ลงนาม
ฟิลด์ tier ใน เนื้อหา ของฟีดที่ลงนามจะเป็น "live" เสมอ — บริการฟีดเผยแพร่
อาร์ติแฟกต์ที่ลงนามแล้วสองรายการต่อหนึ่งเวอร์ชัน: live มีแคมเปญปัจจุบัน ส่วน community
จะไม่รวมแคมเปญเหล่านั้น อาร์ติแฟกต์แต่ละรายการได้รับการลงนามตามไบต์ที่ตรงกันทุกประการของตัวเอง
เนื้อหายังคงไม่ใช้เป็นตัวตัดสินสิทธิ์ ระดับที่เลือกให้กับคำขอจริงๆ จะอยู่ใน
ส่วนหัวการตอบกลับ x-omniroute-feed-tier ซึ่งกำหนดที่ฝั่งเซิร์ฟเวอร์จากคีย์
Authorization ของคำขอ
syncRadar() (src/lib/radar/sync.ts::parseServedTierHeader()) เป็นจุดเดียว
ที่ตัดสินระดับซึ่งไคลเอนต์ควรเชื่อถือ:
- แยกวิเคราะห์
x-omniroute-feed-tierด้วยRadarTierSchema(Zod) — หากไม่มีส่วนหัว หรือค่าที่ได้ไม่ตรงกับ"community"หรือ"live"ทุกประการ จะถือว่า ไม่มี อยู่ (จะไม่นำค่าดังกล่าวเข้าสู่แคช/UI ตามที่ได้รับมาโดยตรง; กรณีนี้ยังครอบคลุมถึงเซิร์ฟเวอร์ฟีด รุ่นเก่าที่มีอยู่ก่อนส่วนหัวนี้ด้วย) - ย้อนกลับไปใช้ฟิลด์
tierของเนื้อหาที่ลงนาม (ซึ่งเป็น"live"เสมอ) เฉพาะเมื่อขั้นตอนที่ 1 ไม่ได้ค่าใดๆ - ระดับที่ตัดสินได้คือค่าที่จะถูกแคชและส่งคืนเป็น
{ status: "updated", version, tier }— แดชบอร์ดจะแสดงค่านี้เสมอ โดยไม่ใช้ฟิลด์ดิบจากเนื้อหา
กฎการผสานโอเวอร์เลย์ขณะอ่าน
applyFeed() (src/lib/radar/applyFeed.ts) ผสานฟีดที่แคชไว้ ทับ เบสไลน์
แบบคงที่ ขณะอ่าน ภายใน getRadarCatalog() อาร์เรย์เบสไลน์
(FREE_MODEL_BUDGETS) จะไม่ถูกแก้ไข — ระบบจะคำนวณ MergedEntry[] ใหม่ทุกครั้งที่
เรียกใช้
มีกฎสี่ข้อ ตามลำดับความสำคัญ:
- ฟีดจะไม่เขียนทับค่าที่โอเวอร์ไรด์ไว้ภายในเครื่อง พิจารณาแยกเป็นรายฟิลด์: หากผู้ดูแลระบบ
ปรับแต่งฟิลด์ของรายการไว้ (
localOverridesmap ซึ่งใช้provider:modelIdเป็นคีย์) ค่าจากฟีดสำหรับฟิลด์นั้นโดยเฉพาะจะถูกข้าม — ค่าของผู้ดูแลระบบมีสิทธิ์เหนือกว่า enabled: falseจะปิดใช้งานรายการพร้อมระบุที่มา รายการในฟีดที่ปิด รายการหนึ่งจะตั้งค่าenabled: falseและdisabledBy: "radar"ในผลลัพธ์ที่ผสานแล้ว เพื่อให้ UI สามารถอธิบายได้ว่า เหตุใด รายการจึงเปลี่ยนจากพร้อมใช้งานเป็นปิดใช้งาน- รายการที่ผู้ใช้เพิ่มและไม่มีอยู่ในฟีดจะยังคงอยู่โดยไม่ถูกแก้ไข รายการที่ มีอยู่เฉพาะในเบสไลน์ (หรือเพิ่มไว้ภายในเครื่อง) และไม่มีรายการฟีดที่ตรงกัน จะถูกส่งผ่านโดยไม่มีการเปลี่ยนแปลง
- รายการที่มี tombstone จะไม่ถูกทำให้กลับมาอีก หากผู้ดูแลระบบลบ
รายการอย่างชัดเจน (
tombstonesset) การที่ฟีดเพิ่มprovider:modelIdนั้นกลับมาใน เวอร์ชันภายหลังจะไม่ทำให้รายการกลับคืนมา
ฟิลด์ที่แก้ไขได้และ tombstone จะถูกจัดเก็บอย่างถาวรใน
radar_local_model_state (ไมเกรชัน 153_radar_local_model_state.sql) อะแดปเตอร์ DB สาธารณะ
(src/lib/db/radar.ts) จะแปลงแถวเหล่านั้นเป็น localOverrides map และ
tombstones set ที่ applyFeed() ใช้; ในระบบใช้งานจริง getRadarCatalog() จะโหลดสถานะดังกล่าว
หลังผ่านการตรวจสอบแฟล็ก แคช และสคีมาแล้ว เฉพาะ displayName และ enabled เท่านั้นที่
ผู้ดูแลระบบแก้ไขได้ ไม่สามารถเขียนข้อมูลอัตลักษณ์ของผู้ให้บริการ/โมเดล ที่มาจากฟีด โควตา ความสามารถ ToS
และข้อมูลการตั้งค่า ผ่านส่วนติดต่อดังกล่าวได้
แดชบอร์ดมีการดำเนินการภายในเครื่องสี่แบบ:
- แก้ไข เปลี่ยนชื่อที่แสดงและสถานะเปิดใช้งานภายในเครื่อง
- รีเซ็ตการเปลี่ยนแปลงภายในเครื่อง ล้างฟิลด์ที่แก้ไขได้ทั้งสองฟิลด์โดยไม่เปลี่ยน tombstone
- ซ่อน สร้าง tombstone เพื่อให้การอัปเดตฟีดในภายหลังไม่สามารถสร้างแถวดังกล่าวขึ้นใหม่ได้
- กู้คืน ลบ tombstone; ค่าโอเวอร์ไรด์ที่บันทึกแยกไว้จะยังคงมีผล
enabled: false จากฟีดยังคงเป็นข้อยกเว้นด้านความปลอดภัย: ค่านี้มีสิทธิ์เหนือกว่า
enabled: true ภายในเครื่องที่ล้าสมัย ทำให้รายการที่ผสานแล้วยังคงถูกปิดใช้งาน และบันทึก
disabledBy: "radar"
การเผยแพร่แคตตาล็อกใช้ schemaVersion: 2 โดย contextWindow และแต่ละฟิลด์ใน tools, vision และ
thinking จะแยกกันเป็น number | null / boolean | null: null หมายถึงไม่ทราบ ขณะที่
false หมายถึงแหล่งข้อมูลอย่างเป็นทางการของผู้ให้บริการที่ผ่านการยืนยันแบบ D16 ระบุไว้อย่างชัดเจนว่าไม่มีความสามารถนั้น
แฟล็กรีจิสทรี/ข้อกำหนดโมเดลภายในของ OmniRoute จะไม่ถูกยกระดับให้เป็นข้อเท็จจริงของฟีดโดยตรง ไคลเอนต์
ยังคงยอมรับสแนปช็อต v1; เนื่องจากตัวสร้างเดิมใช้ false เป็นตัวยึดตำแหน่งสำหรับการไม่มีข้อมูล ค่า false ของ v1
จะถูกปรับให้เป็นไม่ทราบ ขณะที่ true ของ v1 ยังคงถือเป็นข้อเท็จจริง เวอร์ชันสคีมาที่ไม่รู้จักจะไม่ผ่านโดยค่าเริ่มต้น และ
แคชล่าสุดที่ถูกต้องจะยังคงพร้อมใช้งาน ทุกโมเดล v2 ที่มีบริบท/ความสามารถซึ่งไม่ใช่ null ต้องมี
metadataEvidenceUrls[] แบบ HTTPS ที่ไม่ต้องใช้ข้อมูลประจำตัว มิฉะนั้นการตรวจสอบสคีมาจะล้มเหลวและแคชจะ
ไม่ถูกแทนที่ ตารางแคตตาล็อกแสดงสถานะทั้งสามเป็น ✓, ✕ และ ?
คอมโบแบบแนะนำและการเข้าถึง MCP
ค่า familyId ที่ได้รับการยืนยันจะยังคงอยู่หลังการทำโอเวอร์เลย์ขณะอ่าน และขับเคลื่อนโมดูลบริสุทธิ์
buildRadarComboSuggestions() (src/lib/radar/comboSuggestions.ts) ระบบจะแนะนำตระกูลหนึ่ง
ก็ต่อเมื่อมีผู้ให้บริการที่แตกต่างกันอย่างน้อยสองรายซึ่งมีการเชื่อมต่อที่ใช้งานอยู่และนำเสนอ ID โมเดลที่คัดสรรไว้ตรงกันทุกประการ
โมเดลที่ปิดใช้งาน ผู้ให้บริการที่ไม่ได้ใช้งาน ID โมเดลที่ขาดหาย ตระกูลที่มีรายการเดียว และการจับคู่
นามแฝง/คำนำหน้าที่กำกวม จะไม่ผ่านโดยค่าเริ่มต้น คำแนะนำใช้กลยุทธ์ priority ที่มีอยู่ โดยเรียง
งบประมาณรายเดือนแบบเกิดซ้ำที่มากที่สุดไว้ก่อน; UI จะสร้างรายการเหล่านี้ผ่าน POST /api/combos เท่านั้น
UI แบบมีคำแนะนำอยู่ที่ /dashboard/radar/combos โดยอ่านข้อมูลจาก endpoint ภายในเครื่อง
GET /api/radar/catalog และ GET /api/combos/builder/options เท่านั้น และจะไม่เรียกใช้การซิงค์ Radar
ไม่อ่านข้อมูลรับรองของผู้ให้บริการ หรือเขียนข้อมูลลงในฐานข้อมูลคอมโบโดยตรง
ไคลเอ็นต์ MCP สามารถอ่านข้อมูลฉายภาพภายในเครื่องชุดเดียวกันได้ด้วย omniroute_radar_catalog (read:radar) ตัวกรอง
provider, familyId และ enabledOnly ซึ่งเป็นตัวเลือกเสริม จะได้รับการประเมินหลังจากอ่านข้อมูลภายในเครื่องผ่าน
GET /api/radar/catalog หนึ่งครั้ง เอาต์พุตแบบปิดประกอบด้วยเมทาดาทาของแค็ตตาล็อก พร้อมข้อมูลผู้ให้บริการ/โมเดล
ชื่อที่ใช้แสดง, familyId, โควตา, ความสามารถ, สถานะการเปิดใช้งาน, ต้นทาง และ disabledBy โดยจะไม่มีการส่งคืน URL
สำหรับการตั้งค่า ขั้นตอน การเชื่อมต่อ ที่อยู่อีเมล คีย์ และข้อมูลการแนะนำต่อ เครื่องมือนี้เป็นแบบ
อ่านอย่างเดียวและจะไม่เรียกใช้ /api/radar/sync
เครื่องหมายระบุแหล่งที่มา
รายการที่ผสานแล้วทุกรายการจะมีฟิลด์ origin ซึ่ง UI แสดงเป็นป้าย:
"baseline"— ไม่มีการเปลี่ยนแปลงจากแค็ตตาล็อกแบบคงที่ของรุ่นที่เผยแพร่"radar"— มีอย่างน้อยหนึ่งฟิลด์ที่ได้รับการรีเฟรชจากฟีด"local"— ผู้ดำเนินการมีการแทนค่าภายในเครื่องอย่างน้อยหนึ่งรายการสำหรับรายการนี้ (การแทนค่าภายในเครื่อง จะมีลำดับความสำคัญเหนือฟีดตามกฎข้อ 1 เสมอ ไม่ว่าฟีดจะระบุไว้อย่างไรก็ตาม)
พื้นผิวภายใน — ไม่ทำหน้าที่เป็นพร็อกซีฟีดโดยเด็ดขาด
กลุ่มเส้นทาง Radar ภายในด้านล่างรองรับ UI ภายใต้ src/app/api/radar/:
| เส้นทาง | เมธอด | วัตถุประสงค์ |
|---|---|---|
/api/radar/catalog |
GET | ส่งคืนแค็ตตาล็อกที่ผสานแล้ว (getRadarCatalog()) จากแคชภายใน |
/api/radar/sync |
POST | เรียกใช้ syncRadar() ฝั่งเซิร์ฟเวอร์ และส่งคืนสถานะที่ได้ |
/api/radar/settings |
GET | ส่งคืน { optIn, hasSupporterKey, supporterKeyMasked } — ไม่ส่งคืนคีย์ดิบโดยเด็ดขาด |
/api/radar/settings |
POST | กำหนดการยินยอมเข้าร่วมและ/หรือคีย์ผู้สนับสนุน (ที่เข้ารหัสแล้ว) |
/api/radar/referrals |
GET | ส่งคืน { fixed, campaigns, tier } จากแคชภายใน — ดู ลิงก์แนะนำ ด้านล่าง |
/api/radar/offers |
GET | ส่งคืนข้อเสนอที่ใช้งานอยู่จากแคชสดภายในที่ผ่านการตรวจสอบ โดยไม่ส่งคืนคีย์ผู้สนับสนุนโดยเด็ดขาด |
/api/radar/offers/sync |
POST | เรียกใช้ไปป์ไลน์ syncRadarOffers() ฝั่งเซิร์ฟเวอร์ซึ่งใช้เฉพาะคีย์สด |
/api/radar/intel |
GET | ส่งคืน Intel สดภายในที่ผ่านการตรวจสอบ พร้อมค่าบูลีนที่ระบุการรับรองผู้สนับสนุน โดยไม่ส่งคืนข้อมูลระบุตัวตนหรือคีย์ |
/api/radar/intel/sync |
POST | เรียกใช้ไปป์ไลน์ syncRadarIntel() ฝั่งเซิร์ฟเวอร์ซึ่งใช้เฉพาะคีย์สด |
/api/radar/status |
GET | ส่งคืนสถานะการตั้งค่า/แคชภายในแบบอ่านอย่างเดียวสำหรับแค็ตตาล็อก การแนะนำ ข้อเสนอ และ Intel โดยไม่มีข้อมูลลับ |
/api/radar/sync-all |
POST | เรียกใช้โมดูลซิงค์ทั้งสี่รายการฝั่งเซิร์ฟเวอร์ และส่งคืนสถานะแยกกันสำหรับแต่ละฟีด |
/api/radar/local-model-state |
GET | แสดงรายการค่าที่เขียนทับและทูมสโตนที่คงอยู่ สำหรับตัวควบคุมการแก้ไข/คืนค่า |
/api/radar/local-model-state |
PATCH | กำหนดหรือล้างฟิลด์ค่าที่เขียนทับ displayName/enabled ซึ่งผ่านการตรวจสอบแล้ว |
/api/radar/local-model-state |
PUT | สร้างหรือลบทูมสโตนด้วย { provider, modelId, tombstoned } |
/api/radar/local-model-state |
DELETE | ล้างฟิลด์ค่าที่เขียนทับซึ่งแก้ไขได้ โดยยังคงเก็บทูมสโตนไว้ |
กฎตายตัว: เส้นทางเหล่านี้จะไม่ทำหน้าที่เป็นพร็อกซีให้บริการฟีดโดยเด็ดขาด เบราว์เซอร์จะสื่อสาร
กับเซิร์ฟเวอร์ OmniRoute ภายในเท่านั้น โมดูลทั้งสี่ที่เชื่อมต่อกับบริการ Radar ได้แก่
src/lib/radar/sync.ts (แค็ตตาล็อก), src/lib/radar/referralsSync.ts (การแนะนำ) และ
src/lib/radar/offersSync.ts (ข้อเสนอ) รวมถึง src/lib/radar/intelSync.ts (Intel) โดยทั้งหมดทำงาน
ฝั่งเซิร์ฟเวอร์เท่านั้น ไม่เคยทำงานฝั่งไคลเอนต์ วิธีนี้ช่วยป้องกันไม่ให้
URL ของฟีดและคีย์ผู้สนับสนุนใดๆ ปรากฏอยู่ในการรับส่งข้อมูลเครือข่ายที่ไคลเอนต์มองเห็นได้โดยสิ้นเชิง
ปลายทาง Radar ทั้งหมดจะส่งคืน 404 เมื่อปิด RADAR_ENABLED (ดู
แฟล็ก ด้านบน) และประมวลผลการตอบกลับข้อผิดพลาดผ่าน
buildErrorBody()/sanitizeErrorMessage() ตามกฎการล้างข้อมูลข้อผิดพลาดที่ใช้ทั่วทั้งรีโพ
(docs/security/ERROR_SANITIZATION.md)
การยืนยันตัวตน
ปลายทาง Radar ทั้งหมดต้องมีการยืนยันตัวตนผ่าน isAuthenticated()
(src/shared/utils/apiAuth.ts) — โดยใช้คุกกี้เซสชันของแดชบอร์ดหรือคีย์ API ที่มีขอบเขตการจัดการ
ซึ่งเป็นกลไกตรวจสอบเดียวกับที่ปกป้องส่วนอื่นๆ ของ /api/settings/* การตรวจสอบ 404
เมื่อปิดแฟล็กจะทำงาน ก่อน การตรวจสอบการยืนยันตัวตนเสมอ ดังนั้นการติดตั้งที่ปิด RADAR_ENABLED
จะยังคงเหมือนเดิมในระดับไบต์ (ไม่มีข้อความแจ้งให้ยืนยันตัวตนเพียงเพื่อให้ทราบว่าไม่มีพื้นผิวนี้อยู่)
เมื่อเปิดแฟล็กแล้ว คำขอที่ไม่ได้ยืนยันตัวตนจะได้รับ 401 ก่อนที่จะมีการอ่านหรือ
เขียน DB ใดๆ GET /api/radar/settings จะไม่ส่งคืนคีย์ผู้สนับสนุนแบบดิบ ไม่ว่า
สถานะการยืนยันตัวตนจะเป็นอย่างไร — โดยจะส่งคืนเฉพาะรูปแบบที่ปกปิดแล้วและค่าบูลีน hasSupporterKey
ข้อเสนอสำหรับผู้สนับสนุน
ข้อเสนอใช้อาร์ติแฟกต์ที่ลงนามของตนเองผ่าน GET /v1/offers/latest และจะไม่ใช้แคชแค็ตตาล็อกหรือแคชการแนะนำร่วมกัน เซิร์ฟเวอร์เอนด์พอยต์กำหนดให้ใช้คีย์ Bearer ของผู้สนับสนุนแบบ live ที่ถูกต้อง โดยไม่มีการย้อนกลับไปใช้แบบ community ดังนั้น syncRadarOffers() จึงหยุดทำงานก่อนเข้าถึงเครือข่ายเมื่อปิดแฟล็กฟีเจอร์ ผู้ดำเนินการยังไม่ได้เลือกเข้าร่วม หรือยังไม่ได้กำหนดค่าคีย์ผู้สนับสนุน
หลังจาก GET สำเร็จ ไคลเอนต์จะตรวจสอบลายเซ็น Ed25519 บนไบต์ของการตอบกลับที่ตรงตามต้นฉบับ ตรวจสอบความถูกต้องด้วย RadarOffersFeedSchema กำหนดให้ทั้งเนื้อความที่ลงนามและเฮดเดอร์ x-omniroute-feed-tier ระบุว่า live บังคับใช้เวอร์ชันแบบจุดที่ใหม่กว่าอย่างเคร่งครัด และหลังจากนั้นจึงแทนที่ radar_offers_cache แบบอะตอมมิก (ไมเกรชัน 144_radar_offers_cache.sql) โดยใช้ขีดจำกัดรวมเฮดเดอร์และสตรีมที่ 10 MB เช่นเดียวกับฟีดอื่น ความล้มเหลวด้านลายเซ็น สคีมา ระดับ การเล่นซ้ำ ขนาด HTTP และเครือข่าย จะยังคงเก็บรักษาแคชล่าสุดที่ผ่านการตรวจสอบแล้วไว้ทั้งหมด
รูปแบบข้อเสนอแบบปิดรองรับสิทธิประโยชน์สามประเภทที่เปรียบเทียบกันได้ ได้แก่ เปอร์เซ็นต์ในหน่วยเบสิสพอยต์ เครดิตในหน่วยย่อยของสกุลเงิน หรือจำนวนวันทดลองใช้ ข้อเสนอจากพาร์ทเนอร์ต้องมีค่าพื้นฐานสาธารณะประเภทเดียวกัน และสิทธิประโยชน์ต้องมีค่ามากกว่าอย่างเคร่งครัด ส่วนข้อเสนออย่างเป็นทางการไม่มีค่าพื้นฐานของพาร์ทเนอร์ URL ต้องเป็น HTTPS และไม่มีข้อมูลรับรอง getRadarOffers() จะตรวจสอบความถูกต้องของเพย์โหลดที่แคชไว้อีกครั้งเพื่อป้องกันข้อผิดพลาด และกรองรายการที่หมดอายุทุกครั้งที่อ่านจากภายในระบบ ส่วน /dashboard/radar/offers จะกรองวันหมดอายุอีกครั้งก่อนเรนเดอร์ ใช้ข้อความภาษาโปรตุเกสเมื่อมีและย้อนกลับไปใช้ภาษาอังกฤษเมื่อไม่มี พร้อมทั้งระบุข้อเสนอจากพาร์ทเนอร์ไว้อย่างชัดเจน
เบราว์เซอร์เรียกใช้เฉพาะเส้นทางภายในระบบเท่านั้น โดยจะอ่านสแนปช็อตการตั้งค่าที่ปกปิดข้อมูลแล้ว ขอให้ POST /api/radar/offers/sync รีเฟรชข้อมูลฝั่งเซิร์ฟเวอร์ จากนั้นจึงอ่าน GET /api/radar/offers หากไม่มีคีย์ ระบบจะแสดงลิงก์สำหรับผู้มีส่วนร่วม/การสนับสนุนที่มีอยู่เดิมแทนการพยายามส่งคำขอฟีด ลิงก์ข้อเสนอภายนอกจะเปิดในแท็บใหม่ด้วย noopener noreferrer รีลีสนี้ไม่มีการเปิดเผยเครื่องมือ MCP ชื่อ radar_offers
Radar Intel, ป้ายผู้สนับสนุน และ CLI
Intel เป็นอาร์ติแฟกต์ที่ลงนามแล้วที่ GET /v1/intel/latest RadarIntelFeedSchema แบบปิดยอมรับเฉพาะการจัดอันดับ ELO ที่ Radar เป็นเจ้าของ ซึ่งผู้ดูแลแบบส่วนตัวคำนวณจากการเปรียบเทียบที่ได้รับการยืนยันแล้ว และเดลตาตามข้อเท็จจริงของอายุ/จำนวนแค็ตตาล็อกที่คำนวณจากสแนปช็อตแค็ตตาล็อกที่ลงนามแล้ว วิธีการกำหนดค่าเริ่มต้นไว้ตายตัวที่คะแนนเริ่มต้น 1000 และ K=32 การจัดอันดับที่ว่างเปล่าถือว่าถูกต้องเมื่อยังไม่มีการยืนยันการเปรียบเทียบ และไคลเอนต์จะไม่สร้างการจัดอันดับขึ้นเอง
syncRadarIntel() ใช้ Bearer ฝั่งเซิร์ฟเวอร์ การหมดเวลา 30 วินาที ขีดจำกัดสตรีม 10 MiB การตรวจสอบ Ed25519 ตามไบต์ที่ตรงตามต้นฉบับ สคีมาแบบเข้มงวด ข้อกำหนด live สำหรับเนื้อความ/เฮดเดอร์ เวอร์ชันขั้นต่ำ และการเก็บรักษาแคชล่าสุดที่ใช้งานได้ เช่นเดียวกับข้อเสนอ หลังจากบันทึกสแนปช็อตแบบ live ที่ผ่านการตรวจสอบแล้ว ไคลเอนต์จะสร้าง radar:<sha256(supporter key)> จัดเก็บเฉพาะข้อมูลระบุตัวตนแบบทางเดียวนี้ และส่งอีเวนต์การยอมรับ radar_supporter โดยเฉพาะ ป้าย radar-supporter เป็นแบบ idempotent และให้ XP เป็นศูนย์ โดยจะไม่อัปเดตลีดเดอร์บอร์ดหรือนำ token_share กลับมาใช้ซ้ำ /dashboard/radar/intel จะแสดงป้ายจากเมทาดาทาของแคชภายในระบบที่ผ่านการตรวจสอบแล้วเท่านั้น
CLI เปิดให้ใช้ omniroute radar status และ omniroute radar sync ทั้งสองคำสั่งสื่อสารกับ OmniRoute API ภายในระบบเท่านั้น status จะส่ง GET /api/radar/status แบบอ่านอย่างเดียว ส่วน sync จะส่ง POST /api/radar/sync-all หนึ่งครั้งและพิมพ์ผลลัพธ์แยกตามฟีด ทั้งสองคำสั่งจะไม่อ่าน ไม่รับ หรือพิมพ์คีย์ผู้สนับสนุน และจะไม่ติดต่อบริการ Radar โดยตรง
ลิงก์แนะนำ (เครดิตฟรี)
ลิงก์แนะนำให้บริการผ่านฟีดแบบ แยกอิสระและเป็นปัจจุบันเสมอ —
GET /v1/referrals/latest — ซึ่งแยกจากฟีดแค็ตตาล็อก การออกแบบนี้เป็นไปโดยตั้งใจ:
ฟีดแค็ตตาล็อกในระดับ community เป็นสแนปช็อตที่อาจเก่าได้ถึง 30 วัน ดังนั้น
ลิงก์แนะนำที่ดึงออกมาจากฟีดดังกล่าวจึงเคยล้าหลังกว่ารายการลิงก์จริงบนเซิร์ฟเวอร์เป็นระยะเวลา
เท่ากัน (ลิงก์แนะนำที่เพิ่งเพิ่มใหม่อาจยังไม่ส่งถึงผู้ใช้ฟรี/community นานถึงหนึ่งเดือน)
ฟีด referrals ขจัดความล่าช้านี้ด้วยการซิงก์ตามรอบเวลาของตัวเองซึ่งสั้นกว่ามาก
// เนื้อหาการตอบกลับจาก GET /v1/referrals/latest (ลงนามด้วย Ed25519 โดยใช้คีย์ที่ปักหมุดไว้คีย์เดียวกับ
// ฟีดแค็ตตาล็อก):
{
feed: "omniroute-radar-referrals",
schemaVersion: 1,
generatedAt: string, // ISO — กำหนดค่าได้แน่นอน: max(updatedAt) จากลิงก์แนะนำทั้งหมด
// เพื่อให้คำขอสองรายการที่เหมือนกันสร้างไบต์/ลายเซ็นที่ลงนามแล้ว
// เหมือนกันทุกประการ
referrals: {
fixed: RadarReferral[], // มีอยู่ในทุกระดับ รวมถึงแบบไม่มีการยืนยันตัวตน/community
campaigns: RadarReferral[], // มีข้อมูลเฉพาะเมื่อใช้คีย์ Bearer แบบ live (supporter) ที่ถูกต้อง
// คำขอแบบไม่มีการยืนยันตัวตน/ใช้คีย์หมดอายุจะได้รับ []
},
}
// RadarReferral = { provider, url, kind: "fixo" | "campanha", validUntil,
// requiredAction, isDefault }
ฟีดนี้ไม่มีฟิลด์ tier อยู่ในเนื้อหาเลย ซึ่งต่างจากฟีดแค็ตตาล็อก — เซิร์ฟเวอร์เป็นผู้ตัดสินใจ
ว่าจะรวมข้อมูลใดไว้ในแต่ละคำขอตามคีย์ Authorization ดังนั้นส่วนหัวการตอบกลับ
x-omniroute-feed-tier จึงเป็นแหล่งข้อมูลเพียงแห่งเดียวสำหรับระดับที่ให้บริการ
(referralsSync.ts::syncRadarReferrals); หากไม่มีส่วนหัวหรือเป็นค่าที่ไม่รู้จัก ระบบจะลดระดับเป็น
"community" ซึ่งเป็นข้อสันนิษฐานที่มีสิทธิ์น้อยที่สุด RadarReferralsFeedSchema
(src/lib/radar/referralsFeedSchema.ts) ตรวจสอบความถูกต้องของเนื้อหาทั้งหมด โดยใช้
RadarReferralSchema สำหรับลิงก์แนะนำแต่ละรายการตัวเดียวกับที่ส่งออกจาก feedSchema.ts
เพื่อให้ทั้งสองฟีดตรวจสอบลิงก์แนะนำแต่ละรายการด้วยวิธีเดียวกัน RadarReferral.url
ทุกรายการต้องเป็น https:// — url แบบ http:// จะไม่ผ่านการตรวจสอบสคีมา
ฟิลด์ referrals แบบเดิมที่ฝังอยู่ในแค็ตตาล็อกบน RadarFeedSchema (feedSchema.ts)
ยังคงเก็บไว้เพื่อให้เข้ากันได้ย้อนหลังกับฟีดแค็ตตาล็อกที่แคชไว้แล้ว แต่ getRadarReferrals()
จะไม่อ่านฟิลด์ดังกล่าวอีกต่อไป — ดู ตัวเข้าถึง ด้านล่าง
การซิงก์
syncRadarReferrals() (src/lib/radar/referralsSync.ts) เป็นโมดูลเดียวที่
เข้าถึงเครือข่ายสำหรับลิงก์แนะนำ โดยทำตามสัญญาของ syncRadar() ทุกประการ: ปิดแฟล็ก
→ disabled; ไม่ยินยอมเข้าร่วม → opt_out; ดาวน์โหลด ${RADAR_FEED_URL}/v1/referrals/latest
(ใช้การแทนที่สำหรับ fork ผ่าน RADAR_FEED_URL/RADAR_FEED_PUBKEY ชุดเดียวกับแค็ตตาล็อก)
ตรวจสอบลายเซ็น Ed25519 บนไบต์การตอบกลับจริง (verifyFeedBytes) ตรวจสอบกับ
RadarReferralsFeedSchema และแคชลงในตาราง radar_referrals_cache
(ไมเกรชัน 142_radar_referrals_cache.sql) — ซึ่งเป็นตารางที่แยกจาก
radar_feed_cache ของแค็ตตาล็อกโดยสิ้นเชิง ขีดจำกัดขนาดการตอบกลับ 10 MB และค่าขั้นต่ำ
ของ generatedAt จะปฏิเสธฟีดขาเข้าที่เก่ากว่าฟีดในแคช เพื่อป้องกันการนำอาร์ติแฟกต์ที่ลงนามแล้ว
แต่เก่ากว่ามาเล่นซ้ำ ระบบยอมรับการประทับเวลาที่เท่ากัน: เซิร์ฟเวอร์ตั้งใจให้ตัวแปรของลิงก์แนะนำ
แบบ community และ live มี generatedAt ที่กำหนดค่าได้แน่นอนและเหมือนกัน เพื่อให้เพย์โหลด
ที่ลงนามแล้วและระดับที่ให้บริการเปลี่ยนแปลงได้หลังจากเปลี่ยนคีย์ supporter โดยไม่ต้องเปลี่ยน
ชุดลิงก์ที่เป็นข้อมูลพื้นฐาน ฟังก์ชันนี้จะไม่โยนข้อผิดพลาด — แต่จะคืนออบเจ็กต์สถานะเสมอ;
ข้อผิดพลาดจะไม่มีสแต็กเทรซใน reason
ทริกเกอร์สองรายการต่อไปนี้ช่วยให้แคชลิงก์แนะนำพร้อมใช้งานอยู่เสมอ โดยทั้งคู่ทำงานเป็นอิสระจาก รอบเวลา 24 ชั่วโมงของแค็ตตาล็อก:
- ซิงก์เมื่ออ่าน —
GET /api/radar/referralsจะเรียกsyncRadarReferrals()โดยตรงเมื่อไม่มีแคชหรือแคชเก่ากว่าREFERRALS_STALE_MS(1 ชั่วโมง,shouldSyncReferralsOnRead()) ก่อนให้บริการการตอบกลับ กลไกนี้ทำให้ลิงก์แบบ fixed "เป็นปัจจุบันเสมอ" สำหรับการโหลดแดชบอร์ดครั้งถัดไปทันที โดยไม่ต้องรอตัวจับเวลา เบื้องหลังใดๆ - การซิงก์เสริมโดยตัวจัดกำหนดการ —
radarSchedulerTick()(scheduler.ts) จะประเมิน ความเก่าของข้อมูล referrals อย่างเป็นอิสระในการทำงานรายชั่วโมงรอบเดียวกับที่ใช้สำหรับแค็ตตาล็อก และเรียกsyncRadarReferrals()เมื่อถึงกำหนด การทำงานนี้เกิดขึ้นไม่ว่าแค็ตตาล็อกเองจะถึงกำหนด ในรอบนั้นหรือไม่ และไม่ส่งผลต่อรูปแบบของRadarTickResult(เป็นเพียงผลข้างเคียงแบบ best-effort และจะกลืนข้อผิดพลาดไว้)
ตัวเข้าถึง
src/lib/radar/index.ts ส่งออกตัวเข้าถึงแบบอ่านอย่างเดียวสองรายการ ซึ่งทั้งคู่จะไม่โยนข้อผิดพลาด
(ใช้สัญญาการป้องกันแบบเดียวกับ getRadarCatalog() — หากปิดแฟล็ก ไม่มีแคช หรือเพย์โหลด
ในแคชเสียหาย ทุกกรณีจะให้ผลลัพธ์เป็นรูปแบบว่างแทนข้อผิดพลาด):
getRadarReferrals()→{ fixed: RadarReferral[], campaigns: RadarReferral[] }อ่านจากradar_referrals_cache(ผ่านgetRadarReferralsCache()) และตรวจสอบ ผ่านRadarReferralsFeedSchema— ไม่ใช่ แคชแค็ตตาล็อกgetDefaultReferralFor(provider)→ ลิงก์แนะนำfixedที่มีisDefault: trueสำหรับ provider นั้น หรือnullโดยจะตรวจเฉพาะfixed— จะไม่มีการใช้แคมเปญเป็นลิงก์ "เริ่มต้น" ของ provider
กฎจริงสำหรับ "ลิงก์แนะนำใดเป็นค่าเริ่มต้นของ provider" อยู่ใน
findDefaultReferral() (src/lib/radar/referrals.ts) ซึ่งเป็นฟังก์ชันบริสุทธิ์ขนาดเล็กที่
ไม่มีการนำเข้า DB — จึงสามารถนำเข้าไปยังคอมโพเนนต์ "use client" ได้อย่างปลอดภัย
getRadarReferrals/getDefaultReferralFor (ใน index.ts) ดึง @/lib/db/radar
เข้ามา จึงต้องคงไว้สำหรับฝั่งเซิร์ฟเวอร์เท่านั้น; แดชบอร์ด providers นำเข้า referrals.ts
โดยตรงแทน index.ts (ดูด้านล่าง) เพื่อหลีกเลี่ยงการรวม better-sqlite3 ไว้ในบันเดิล
สำหรับเบราว์เซอร์
GET /api/radar/referrals
ใช้ลำดับเกตเดียวกันทุกประการกับเส้นทาง Radar อื่นทั้งหมด: ปิด RADAR_ENABLED →
404 (ตรวจสอบก่อนเป็นอันดับแรก โดยให้ผลลัพธ์ระดับไบต์เหมือนเดิมทุกประการ); ไม่ได้ยืนยันตัวตน → 401; มิฉะนั้น
จะทริกเกอร์การซิงค์เมื่ออ่าน (ดูด้านบน) เมื่อข้อมูลล้าสมัย จากนั้นตอบกลับ 200 พร้อม
{ fixed, campaigns, tier } — tier มาจากแถวแคชโดยตรง (ซึ่งอาจเพิ่งได้รับการรีเฟรช)
และมีไว้เพื่อให้ข้อมูลเท่านั้น (ใช้กำหนดข้อความเชิญชวนให้อัปเกรดแบบนุ่มนวลของ UI ด้านล่าง) เส้นทางนี้จะไม่
ทำหน้าที่เป็นพร็อกซีไปยังเซิร์ฟเวอร์ฟีดโดยตรง — ซอร์สของเส้นทางเองไม่มีการเรียก fetch(;
การเชื่อมต่อเครือข่ายจะเกิดขึ้นภายใน syncRadarReferrals() เท่านั้น ตามหลักการใช้เฉพาะ
แคชภายในแบบเดียวกับ /api/radar/catalog
UI แดชบอร์ด — แท็บ "เครดิตฟรี" บน /dashboard/radar
นำหน้า Radar ที่มีอยู่ (src/app/(dashboard)/dashboard/radar/page.tsx) มาใช้ซ้ำเป็น
แท็บที่สองแทนการสร้างเส้นทางใหม่ — ลดขอบเขตของการกำหนดเส้นทาง/i18n สำหรับฟีเจอร์ที่เป็น
รูปแบบหนึ่งของข้อมูลซึ่งหน้านี้ดึงอยู่แล้ว หลังจากเลือกเข้าร่วม แถบแท็บจะแสดง
แค็ตตาล็อก (ตารางที่มีอยู่) และ เครดิตฟรี:
- ลิงก์แบบคงที่จะจัดกลุ่มตามผู้ให้บริการ โดยแต่ละรายการจะแสดง
requiredAction(เมื่อมี) และปุ่มที่มีtarget="_blank" rel="noopener noreferrer"ไปยัง URL การแนะนำ - แคมเปญจะแสดงข้อมูลแบบเดียวกัน พร้อม
validUntilเมื่อมี - เมื่อ
campaignsว่างเปล่า และ ระดับที่ให้บริการคือcommunityUI จะแสดง ข้อความเชิญชวนให้อัปเกรดสั้นๆ ("แคมเปญระยะเวลาจำกัดเป็นสิทธิพิเศษสำหรับผู้สนับสนุน") — ข้อความนี้ จะไม่ ซ่อนหรือจำกัดรายการลิงก์แบบคงที่ ซึ่งยังคงแสดงครบถ้วนสำหรับทุกระดับ การเชิญชวนให้ อัปเกรดเป็นเพียงข้อความแบบนุ่มนวลเท่านั้น ไม่ใช่การปิดกั้น
ลิงก์แนะนำบนชื่อผู้ให้บริการ (แดชบอร์ดผู้ให้บริการ)
ProviderPageHeader (src/app/(dashboard)/dashboard/providers/[id]/components/)
ลิงก์ชื่อผู้ให้บริการไปยัง providerInfo.website อยู่แล้วเมื่อมีค่า โดยมีกรณีตัวอย่างหนึ่ง
สำหรับลิงก์ที่สร้างรายได้ คือหมายเหตุลิงก์พันธมิตรของ Kimi (Moonshot AI)
(คีย์ i18n providers.kimiPartnerLinkNote) D28 นำรูปแบบหมายเหตุแบบแนบเนียนเดียวกันนี้
มาใช้ซ้ำสำหรับลิงก์แนะนำเริ่มต้นของ Radar แทนที่จะเพิ่มคีย์ใหม่
ออกแบบให้เชื่อมโยงกันอย่างหลวมๆ:
resolveProviderHeaderLink()(src/app/(dashboard)/dashboard/providers/providerPageUtils.ts) เป็นฟังก์ชัน บริสุทธิ์ —(staticWebsite, referralUrl) => { website, isReferralLink }— โดยไม่ขึ้นต่อ@/lib/radarหรือ@/lib/db/*และproviderPageUtils.tsทั้งไฟล์ยังคงไม่มีการนำเข้าเหล่านั้น (ตรวจสอบยืนยันโดยtests/unit/provider-header-referral-link.test.ts)ProviderDetailPageClient.tsx(คอมโพเนนต์"use client") เป็นเพียงจุดเดียวที่ได้รับอนุญาต ให้ดึงข้อมูล Radar — ผ่านfetch("/api/radar/referrals")ตามรูปแบบการใช้เส้นทางภายใน แบบเดียวกับที่หน้าแดชบอร์ด Radar ใช้ — และคำนวณลิงก์แนะนำเริ่มต้นฝั่งไคลเอนต์ ด้วยfindDefaultReferral()จากsrc/lib/radar/referrals.tsซึ่งไม่ขึ้นกับ DB- เมื่อปิด
RADAR_ENABLEDการดึงข้อมูลจะได้ 404,referralUrlจะยังคงเป็นnullและresolveProviderHeaderLink()จะคืนค่าwebsiteแบบคงที่จากแค็ตตาล็อกโดยไม่เปลี่ยนแปลง — หน้าผู้ให้บริการจะเหมือนกับก่อนมีฟีเจอร์นี้ทุกไบต์ ผลลัพธ์เดียวกันนี้จะเกิดขึ้นเมื่อ ยังไม่มีแคช หรือไม่มีลิงก์แนะนำเริ่มต้นสำหรับผู้ให้บริการรายนั้น - เมื่อลิงก์แนะนำเริ่มต้นมีผลใช้งาน
ProviderPageHeaderจะได้รับisReferralLinkและแสดงหมายเหตุ/คำแนะนำเครื่องมือแบบแนบเนียนเดียวกับลิงก์พันธมิตรของ Kimi (นำคีย์providers.kimiPartnerLinkNoteมาใช้ซ้ำ) — โดยไม่สร้างรูปแบบการแสดงผลใหม่ที่แยกต่างหาก
วิธีโฮสต์ฟีดด้วยตนเอง
ฟอร์กหรือผู้ที่โฮสต์ด้วยตนเองซึ่งต้องการควบคุมแค็ตตาล็อกอย่างเต็มรูปแบบสามารถเรียกใช้บริการ ฟีดของตนเองได้โดยไม่ต้องแก้ไขโค้ดไคลเอนต์:
- ให้บริการเอนด์พอยต์
GET /v1/catalog/latestซึ่งส่งคืนเนื้อหา JSON ที่เป็นไปตามRadarFeedSchema(src/lib/radar/feedSchema.ts) — โดยมีfeed: "omniroute-radar",schemaVersion: 2,version,tier,providers,models,quirksและtotalsที่ระดับบนสุด รองรับx-omniroute-radar-schema: 2; เซิร์ฟเวอร์ที่รองรับ ช่วงเปลี่ยนผ่านควรกำหนดให้คำขอที่ไม่มีเฮดเดอร์นี้ใช้สิ่งประดิษฐ์ v1 ที่ลงนามแยกต่างหากโดยปริยาย - ลงนามไบต์ของการตอบกลับตามจริงด้วยคู่คีย์ Ed25519 และส่งคืนลายเซ็นแบบ base64
ในเฮดเดอร์การตอบกลับ
x-omniroute-feed-signature - ตั้งค่า
RADAR_FEED_URLเป็น URL ฐานใหม่ และตั้งค่าRADAR_FEED_PUBKEYเป็น คีย์สาธารณะที่ตรงกัน (SPKI แบบ base64-DER หรือ PEM) — ดู ข้อมูลอ้างอิงตัวแปรสภาพแวดล้อม - เปิดใช้งาน
RADAR_ENABLEDและเลือกรับผ่านPOST /api/radar/settings({ optIn: true })
ไม่จำเป็นต้องเปลี่ยนแปลงโค้ดอื่นใด — verifyFeedBytes() จะนำค่าที่กำหนดทับมาใช้
โดยอัตโนมัติ (getFeedPublicKeys() ใน src/lib/radar/pinnedKeys.ts) และการเปรียบเทียบ
เวอร์ชัน การตรวจสอบสคีมา และกฎการผสานจะถูกใช้กับฟีดที่โฮสต์ด้วยตนเองในลักษณะเดียวกัน
ลิงก์แนะนำ (ดู ลิงก์แนะนำ (เครดิตฟรี)
ด้านบน) เป็นสิ่งประดิษฐ์ที่แยกต่างหากและไม่บังคับ: ฟอร์กที่ให้บริการเฉพาะ /v1/catalog/latest
ยังคงทำงานได้อย่างสมบูรณ์ — syncRadarReferrals() จะลดระดับเป็น { status: "error" } เมื่อได้รับ 404
จาก /v1/referrals/latest และแคชจะยังคงว่างอยู่ ดังนั้น
GET /api/radar/referrals จึงยังคงส่งคืน { fixed: [], campaigns: [], tier: null }
แทนที่จะทำให้ส่วนที่เหลือของหน้าล้มเหลว หากต้องการเสนอลิงก์แนะนำด้วย ให้บริการ
GET /v1/referrals/latest ที่เป็นไปตาม RadarReferralsFeedSchema
(src/lib/radar/referralsFeedSchema.ts) และลงนามด้วยคู่คีย์ Ed25519 เดียวกับ
ฟีดแค็ตตาล็อก
ข้อเสนอสำหรับผู้สนับสนุนเป็นสิ่งประดิษฐ์ทางเลือกอีกประเภทหนึ่ง หากต้องการให้บริการ ให้ติดตั้งใช้งาน
GET /v1/offers/latest ด้วย RadarOffersFeedSchema แบบปิด
(src/lib/radar/offersFeedSchema.ts) กำหนดให้ต้องมีสิทธิ์ใช้งานที่ยังมีผล ส่งคืน
x-omniroute-feed-tier: live และลงนามไบต์ตามจริงด้วยคีย์เดียวกัน ฟอร์กที่ไม่มี
เอนด์พอยต์นี้จะยังคงพฤติกรรมของแค็ตตาล็อก/การแนะนำไว้โดยไม่เปลี่ยนแปลง; การรีเฟรชข้อเสนอจะล้มเหลวโดยไม่ทำลายข้อมูล
และแคชข้อเสนอในเครื่องที่ได้รับการตรวจสอบล่าสุดจะยังคงใช้งานได้
Intel เป็นทางเลือกในลักษณะเดียวกัน ผู้ที่โฮสต์ด้วยตนเองสามารถให้บริการ GET /v1/intel/latest โดยใช้
RadarIntelFeedSchema (src/lib/radar/intelFeedSchema.ts) กำหนดให้ต้องมีสิทธิ์ใช้งานที่ยังมีผล ส่งคืน
x-omniroute-feed-tier: live และลงนามไบต์ตามจริงด้วยคีย์ Ed25519 ที่ใช้ร่วมกัน การไม่มี
เอนด์พอยต์นี้จะไม่ส่งผลต่อแค็ตตาล็อก การแนะนำ และข้อเสนอ; การรีเฟรช Intel จะเก็บสแนปช็อต
ในเครื่องที่ได้รับการตรวจสอบล่าสุดไว้
เอกสารที่เกี่ยวข้อง
docs/security/ERROR_SANITIZATION.md— รูปแบบ การตอบกลับข้อผิดพลาดที่เส้นทาง/api/radar/*ใช้docs/reference/ENVIRONMENT.md— ข้อมูลอ้างอิงRADAR_FEED_URL/RADAR_FEED_PUBKEY