Files
OmniRoute/docs/i18n/th/docs/frameworks/RADAR.md
Diego Rodrigues de Sa e Souza 8feea123bb feat(docs): mirror every docs/ page in all 65 locales (#14106)
* 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.
2026-09-18 13:16:46 -03:00

124 KiB
Raw Blame History

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) จะ render notFound()
  • 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" }    — ไม่มีการเรียกเครือข่าย

เมื่อเปิดใช้งานทั้งสองอย่าง เส้นทางการซิงค์จะเป็นดังนี้:

  1. GET <feed base URL>/v1/catalog/latest พร้อม x-omniroute-radar-schema: 2 และ header Authorization: Bearer <supporter key> ซึ่งเป็นตัวเลือก (ดูด้านล่าง) โดยค่าเริ่มต้น server จะใช้อาร์ติแฟกต์เปลี่ยนผ่าน v1 ที่ลงนามแยกต่างหากเมื่อไม่มี schema header เพื่อให้ client รุ่นเก่าที่ติดตั้งไว้ยังคง ได้รับการอัปเดต
  2. นี่เป็น flow ของแอปพลิเคชันที่ดาวน์โหลดเพียงอย่างเดียว แต่ยังคงเป็นคำขอ HTTPS โครงสร้างพื้นฐาน ที่โฮสต์อยู่จะได้รับ metadata การเชื่อมต่อตามปกติ เช่น IP ต้นทาง เมื่อมีการกำหนดค่า supporter key การซิงค์จะส่ง key นั้นใน Bearer header ด้วย เพื่อให้บริการสามารถตรวจสอบ สิทธิ์ได้ ณ revision ที่แน่นอนของ private server ซึ่งระบุอยู่ในขอบเขตหลักฐานด้านบน การบันทึกบัญชีคำขอ feed จะใช้ hash ของ key, การใช้งานแบบรวม และ HMAC แบบตัดทอนของ IP ที่หมุนเวียนรายวันสำหรับการตรวจสอบการใช้งานในทางที่ผิดด้วยตนเอง; ตารางเหล่านั้นจะไม่จัดเก็บทั้ง key และ IP ในรูปแบบดิบ บันทึกการเข้าถึงโครงสร้างพื้นฐานและ delivery outbox ที่เข้ารหัสเป็นขอบเขตการปฏิบัติงาน ที่แยกจากกัน
  3. OmniRoute จะไม่ส่ง prompt, response, การสนทนา, ข้อมูลรับรองของ provider, traffic ของโมเดล, uptime, latency หรือการกำหนดค่า provider ภายในเครื่องไปยังบริการ Radar
  4. 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 อันดับแรก อันดับ 110 ในการจัดอันดับรายสัปดาห์ฉบับสมบูรณ์ล่าสุด ใช้งานแบบสด 365 วัน ขอรับได้ตามต้องการ; การหลุดจากอันดับไม่ทำให้ระยะเวลาที่ได้รับสั้นลง
ผู้มีส่วนร่วม 100 อันดับแรก อันดับ 11100 ในการจัดอันดับดังกล่าว ใช้งานแบบสด 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 วัน และอันดับ 11100 ได้รับสิทธิ์ 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 ทั้งหมด เนื่องจากการติดตั้งภายในเครื่อง ไม่เคยได้รับอีเมลของผู้ซื้อ/ผู้มีส่วนร่วม และไม่สามารถสร้างคีย์ดิบขึ้นใหม่จากการตั้งค่าที่เข้ารหัสไว้ได้

  1. ส่งอีเมลที่เชื่อมโยงกับคีย์ บริการจะแสดงหน้าตอบรับแบบเดียวกัน ไม่ว่าจะมี ไลเซนส์ที่กู้คืนได้หรือไม่ก็ตาม เพื่อไม่ให้แบบฟอร์มเปิดเผยว่าบัญชีใดมีอยู่
  2. หากมีสิทธิ์ ระบบส่งมอบจะส่งลิงก์อายุสั้นที่ใช้ได้ครั้งเดียว เมื่อเปิดลิงก์ ระบบจะย้าย โทเค็นไปยังคุกกี้ HttpOnly/Secure ชั่วคราวที่เข้ารหัสทันที และเปลี่ยนเส้นทางไปยัง URL /recover ที่ไม่มีพารามิเตอร์ หน้าดังกล่าวจะไม่มีโทเค็น อีเมล คีย์เก่า หรือคีย์ทดแทน
  3. ยืนยันการเพิกถอน บริการส่วนตัวจะเพิกถอนคีย์เดิม สร้างคีย์ทดแทนโดยใช้ แผน/วันหมดอายุเดิม และจัดเข้าคิวสำหรับส่งทางอีเมลภายในธุรกรรมเดียวกัน ระบบจะไม่ ส่งคีย์ทดแทนกลับไปยังเบราว์เซอร์
  4. วางคีย์ทดแทนลงใน /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 ภายในเครื่องจะเข้ารหัสคีย์ ซิงค์อาร์ติแฟกต์ที่ลงนามแล้วจากฝั่งเซิร์ฟเวอร์ และแนะนำการตั้งค่าผู้ให้บริการ ลำดับการตรวจสอบแบบมีผู้ช่วยคือ:

  1. รับคีย์ที่ออกใหม่หรือกู้คืนจากขั้นตอน contributor claim, plans/checkout, กระบวนการกู้คืน หรือผู้ดูแลเซิร์ฟเวอร์ส่วนตัวที่ได้รับอนุญาต อย่าวางคีย์ดิบลงในบันทึก ภาพหน้าจอ ความคิดเห็นใน issue หรืออาร์กิวเมนต์บรรทัดคำสั่ง
  2. เปิดใช้งาน feature flag RADAR_ENABLED บนการติดตั้ง OmniRoute ภายในเครื่อง การดำเนินการนี้จะแสดง UI แต่จะยังไม่มีการสื่อสารผ่านเครือข่ายจนกว่าจะบันทึกการยินยอมเข้าร่วมแยกต่างหาก
  3. เปิด /dashboard/radar วางคีย์ แล้วเปิดใช้งาน เบราว์เซอร์จะส่งคำขอภายในเครื่องหนึ่งครั้งไปยัง POST /api/radar/settings พร้อม { optIn: true, supporterKey }; คีย์จะถูกเข้ารหัสภายในเครื่อง และ การตอบกลับจะมีเพียง omr_****<last4>
  4. ปล่อยให้หน้าจอเปิดใช้งานดำเนินการซิงค์แค็ตตาล็อก หรือเลือก ซิงค์ตอนนี้ ยืนยันว่าหน้า แสดงสถานะ live เวอร์ชันฟีด และเวลาที่ดึงข้อมูล สำหรับการวินิจฉัยภายในเครื่องที่ผ่านการตรวจสอบสิทธิ์ GET /api/radar/status จะรายงานสถานะการยินยอมเข้าร่วม/การมีอยู่ของคีย์ และสถานะแคชทั้งสี่ โดยไม่ส่งคืน คีย์ POST /api/radar/sync-all สามารถรีเฟรชแค็ตตาล็อก การแนะนำต่อ ข้อเสนอ และ Intel ได้อย่างชัดเจน
  5. เปิด /dashboard/radar/setup?provider=<provider> ไปยัง URL สำหรับข้อมูลรับรองที่ผู้ให้บริการเป็นผู้ดูแล เลือก เพิ่ม API key บันทึกผ่านแบบฟอร์มจริงของผู้ให้บริการ กลับไปยังคู่มือ แล้วเรียกใช้ ทดสอบการเชื่อมต่อ คู่มือใช้เส้นทางปกติ /api/providers และ /api/providers/<connection-id>/test; โดยจะไม่สร้างข้อมูลรับรอง Radar แยกต่างหาก
  6. เปิด /dashboard/radar/combos หลังจากมีการเชื่อมต่อผู้ให้บริการที่เข้ากันได้อย่างน้อยสองรายการทำงานอยู่ ตรวจสอบกลุ่มที่แนะนำและสร้าง combo ผ่าน combo API ที่มีอยู่ ข้อเสนอและ Intel ยังคงเป็นแคชแบบลงนามที่ใช้ได้เฉพาะ live ซึ่งแยกจากกัน และสามารถตรวจสอบได้ในหน้า Radar เฉพาะของแต่ละรายการ
  7. โหลด /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 ที่กำหนดค่าไม่ถูกต้องหรือเป็นอันตราย (หรือระบบต้นทางที่ส่งข้อมูลขยะ) ไม่ใช่แค็ตตาล็อกที่ถูกต้อง การบังคับใช้มีสองชั้น:

  1. การตรวจสอบ Content-Length ล่วงหน้าจะข้ามการอ่านเนื้อหาทั้งหมดเมื่อ ส่วนหัวประกาศค่าที่เกินขีดจำกัดอยู่แล้ว
  2. การตรวจสอบผลรวมสะสมขณะอ่านเนื้อหาจะบังคับใช้ขีดจำกัดแม้ว่า 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()) เป็นจุดเดียว ที่ตัดสินระดับซึ่งไคลเอนต์ควรเชื่อถือ:

  1. แยกวิเคราะห์ x-omniroute-feed-tier ด้วย RadarTierSchema (Zod) — หากไม่มีส่วนหัว หรือค่าที่ได้ไม่ตรงกับ "community" หรือ "live" ทุกประการ จะถือว่า ไม่มี อยู่ (จะไม่นำค่าดังกล่าวเข้าสู่แคช/UI ตามที่ได้รับมาโดยตรง; กรณีนี้ยังครอบคลุมถึงเซิร์ฟเวอร์ฟีด รุ่นเก่าที่มีอยู่ก่อนส่วนหัวนี้ด้วย)
  2. ย้อนกลับไปใช้ฟิลด์ tier ของเนื้อหาที่ลงนาม (ซึ่งเป็น "live" เสมอ) เฉพาะเมื่อขั้นตอนที่ 1 ไม่ได้ค่าใดๆ
  3. ระดับที่ตัดสินได้คือค่าที่จะถูกแคชและส่งคืนเป็น { status: "updated", version, tier } — แดชบอร์ดจะแสดงค่านี้เสมอ โดยไม่ใช้ฟิลด์ดิบจากเนื้อหา

กฎการผสานโอเวอร์เลย์ขณะอ่าน

applyFeed() (src/lib/radar/applyFeed.ts) ผสานฟีดที่แคชไว้ ทับ เบสไลน์ แบบคงที่ ขณะอ่าน ภายใน getRadarCatalog() อาร์เรย์เบสไลน์ (FREE_MODEL_BUDGETS) จะไม่ถูกแก้ไข — ระบบจะคำนวณ MergedEntry[] ใหม่ทุกครั้งที่ เรียกใช้

มีกฎสี่ข้อ ตามลำดับความสำคัญ:

  1. ฟีดจะไม่เขียนทับค่าที่โอเวอร์ไรด์ไว้ภายในเครื่อง พิจารณาแยกเป็นรายฟิลด์: หากผู้ดูแลระบบ ปรับแต่งฟิลด์ของรายการไว้ (localOverrides map ซึ่งใช้ provider:modelId เป็นคีย์) ค่าจากฟีดสำหรับฟิลด์นั้นโดยเฉพาะจะถูกข้าม — ค่าของผู้ดูแลระบบมีสิทธิ์เหนือกว่า
  2. enabled: false จะปิดใช้งานรายการพร้อมระบุที่มา รายการในฟีดที่ปิด รายการหนึ่งจะตั้งค่า enabled: false และ disabledBy: "radar" ในผลลัพธ์ที่ผสานแล้ว เพื่อให้ UI สามารถอธิบายได้ว่า เหตุใด รายการจึงเปลี่ยนจากพร้อมใช้งานเป็นปิดใช้งาน
  3. รายการที่ผู้ใช้เพิ่มและไม่มีอยู่ในฟีดจะยังคงอยู่โดยไม่ถูกแก้ไข รายการที่ มีอยู่เฉพาะในเบสไลน์ (หรือเพิ่มไว้ภายในเครื่อง) และไม่มีรายการฟีดที่ตรงกัน จะถูกส่งผ่านโดยไม่มีการเปลี่ยนแปลง
  4. รายการที่มี tombstone จะไม่ถูกทำให้กลับมาอีก หากผู้ดูแลระบบลบ รายการอย่างชัดเจน (tombstones set) การที่ฟีดเพิ่ม 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_ENABLED404 (ตรวจสอบก่อนเป็นอันดับแรก โดยให้ผลลัพธ์ระดับไบต์เหมือนเดิมทุกประการ); ไม่ได้ยืนยันตัวตน → 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 ว่างเปล่า และ ระดับที่ให้บริการคือ community UI จะแสดง ข้อความเชิญชวนให้อัปเกรดสั้นๆ ("แคมเปญระยะเวลาจำกัดเป็นสิทธิพิเศษสำหรับผู้สนับสนุน") — ข้อความนี้ จะไม่ ซ่อนหรือจำกัดรายการลิงก์แบบคงที่ ซึ่งยังคงแสดงครบถ้วนสำหรับทุกระดับ การเชิญชวนให้ อัปเกรดเป็นเพียงข้อความแบบนุ่มนวลเท่านั้น ไม่ใช่การปิดกั้น

ลิงก์แนะนำบนชื่อผู้ให้บริการ (แดชบอร์ดผู้ให้บริการ)

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 มาใช้ซ้ำ) — โดยไม่สร้างรูปแบบการแสดงผลใหม่ที่แยกต่างหาก

วิธีโฮสต์ฟีดด้วยตนเอง

ฟอร์กหรือผู้ที่โฮสต์ด้วยตนเองซึ่งต้องการควบคุมแค็ตตาล็อกอย่างเต็มรูปแบบสามารถเรียกใช้บริการ ฟีดของตนเองได้โดยไม่ต้องแก้ไขโค้ดไคลเอนต์:

  1. ให้บริการเอนด์พอยต์ 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 ที่ลงนามแยกต่างหากโดยปริยาย
  2. ลงนามไบต์ของการตอบกลับตามจริงด้วยคู่คีย์ Ed25519 และส่งคืนลายเซ็นแบบ base64 ในเฮดเดอร์การตอบกลับ x-omniroute-feed-signature
  3. ตั้งค่า RADAR_FEED_URL เป็น URL ฐานใหม่ และตั้งค่า RADAR_FEED_PUBKEY เป็น คีย์สาธารณะที่ตรงกัน (SPKI แบบ base64-DER หรือ PEM) — ดู ข้อมูลอ้างอิงตัวแปรสภาพแวดล้อม
  4. เปิดใช้งาน 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 จะเก็บสแนปช็อต ในเครื่องที่ได้รับการตรวจสอบล่าสุดไว้


เอกสารที่เกี่ยวข้อง