Перейти к содержимому
OmniRoute source

Radar Free-Model Catalog (Русский)

Приведённый ниже статус разграничивает возможности, реализованные в этом OSS-релизе, и последующие направления разработки Radar. Это статус на уровне кода, а не обещание того, что конкретное размещённое развёртывание или внешняя интеграция доступны в настоящее время.

Область Статус в этом релизе
Клиент подписанного каталога Реализован под управлением RADAR_ENABLED, с отдельным явным подключением, проверкой Ed25519, локальными зашифрованными настройками/кэшем, постоянными переопределениями отображения/включения, обратимыми маркерами удаления, планировщиком и панелью управления.
Активация для участников Панель управления содержит ссылку на размещённый на сервере процесс получения прав через GitHub и принимает существующий ключ omr_…. Право участника определяется закрытым сервисом; OSS-клиент не содержит токена GitHub или логики выдачи ключей.
Активация по ключу сторонника Реализована. Исходный ключ проверяется, хранится в зашифрованном виде, маскируется при чтении и отправляется только при серверной синхронизации. Изменение или удаление ключа делает недействительными все четыре кэша фидов, зависящие от прав доступа.
Реферальные ссылки Реализованы как отдельно подписанный фид, обновляемый каждый час. Постоянные ссылки сразу доступны уровню сообщества; ограниченные кампании остаются динамическими данными уровня live.
Предложения для сторонников Реализованы как отдельный подписанный фид, доступный только в live-режиме, и отдельная страница панели управления. Клиент повторно проверяет закрытую схему преимуществ, сохраняет последний корректный кэш, отфильтровывает истёкшие записи и явно помечает предложения партнёров.
Аналитика и признание сторонников Реализованы как строгий подписанный фид, доступный только в live-режиме, с принадлежащим Radar рейтингом ELO, фактическими данными об актуальности/тенденциях каталога, проверенным локальным значком сторонника, страницей панели управления и локальными CLI-командами статуса/синхронизации.
Платежи и транзакционные письма Не реализованы в OSS-клиенте. Покупка, пожертвование, проверка квитанций, восстановление и доставка писем относятся к закрытому сервису; доступность размещённого сервиса по-прежнему зависит от его контролируемого развёртывания и конфигурации провайдеров.
Направление исследовательского агента Не входит в этот выпуск клиента. Содержимое курируемых фидов остаётся серверными данными; в установке OmniRoute не запускается автономный исследовательский агент.

Универсальное средство чтения объявлений работает отдельно от флага функции Radar. Главная страница панели управления и средство просмотра журнала изменений получают публичный файл news.json репозитория с помощью обычного запроса GET к NEWS_JSON_URL (src/shared/utils/releaseNotes.ts). Они не отправляют настройки Radar, запросы, конфигурацию провайдера, записи об использовании или локальное состояние отклонения объявлений.

news.json использует закрытую схему v2, реализованную функцией parseNewsPayload():

  • schemaVersion: 2 и ограниченная коллекция items[];
  • стабильные и уникальные значения id объявлений;
  • явные поля active и publishedAt в формате ISO;
  • обязательный текст на английском языке с необязательными локализованными версиями;
  • необязательные HTTPS-ссылки, не требующие учётных данных, и значок из списка разрешённых;
  • выбор самого нового активного объявления, откат локали к английскому языку и локальное отклонение для каждого ID.

Парсер временно принимает прежнюю форму с единственным объектом { active, title, message, ... }, чтобы старые форки могли выполнить миграцию без нарушения работы представления журнала изменений. Недопустимые ленты не оказывают никакого воздействия. Запись о запуске Radar поставляется с active: false; изменение этого значения на true является отдельным действием по выпуску после слияния и развёртывания и не изменяет RADAR_ENABLED или независимое согласие на синхронизацию ленты.


Radar полностью контролируется флагом функции RADAR_ENABLED (src/shared/constants/featureFlagDefinitions.ts, категория policies, defaultValue: "false").

Когда флаг отключён, эта функциональность недоступна:

  • Все конечные точки /api/radar/*, включая локальные операции чтения и записи состояния модели, возвращают 404 до обращения к любому модулю Radar.
  • Экраны панели управления (/dashboard/radar, /dashboard/radar/setup, /dashboard/radar/combos, /dashboard/radar/offers, /dashboard/radar/intel) отображают notFound().
  • getRadarCatalog() (src/lib/radar/index.ts) возвращает неизменённые исходные данные — то же количество записей, те же значения, каждая запись помечена origin: "baseline" — и никогда не читает кэш ленты.
  • Сетевые вызовы Radar никогда не выполняются; каждый модуль синхронизации возвращает { status: "disabled" } до обращения к fetch.

Это строгий общий переключатель: включение флага разблокирует только экраны и ничего более. Оно не приводит к отправке данных, не запускает фоновую синхронизацию и не изменяет маршрутизацию или выбор модели — см. отдельное согласие ниже.


Синхронизация данных требует ОТДЕЛЬНОГО согласия — гарантия конфиденциальности

Заголовок раздела «Синхронизация данных требует ОТДЕЛЬНОГО согласия — гарантия конфиденциальности»

Включение RADAR_ENABLED лишь разблокирует пользовательский интерфейс. Для синхронизации ленты требуется второе, независимое согласие, хранящееся в radar_settings.opt_in (src/lib/db/radar.ts, миграция 136_radar_cache_settings.sql). syncRadar() проверяет флаг и согласие перед выполнением любого сетевого вызова:

Флаг отключён → { status: "disabled" } — сетевой вызов не выполняется
Согласие не дано → { status: "opt_out" } — сетевой вызов не выполняется

Когда включены оба параметра, процесс синхронизации выглядит следующим образом:

  1. GET <базовый URL ленты>/v1/catalog/latest с x-omniroute-radar-schema: 2 и необязательным заголовком Authorization: Bearer <ключ участника программы поддержки> (см. ниже). При отсутствии заголовка схемы серверы по умолчанию предоставляют отдельно подписанный переходный артефакт v1, поэтому ранее установленные клиенты продолжают получать обновления.
  2. Это поток приложения, предназначенный только для скачивания, однако он всё равно представляет собой HTTPS-запрос. Размещённая инфраструктура получает обычные метаданные подключения, например исходный IP-адрес. Если настроен ключ участника программы поддержки, синхронизация также отправляет этот ключ в заголовке Bearer, чтобы сервис мог определить права доступа. В точной ревизии частного сервера, указанной выше в описании границ доказательной базы, для учёта запросов к ленте используются хэши ключей, агрегированные данные об использовании и ежедневно изменяемый усечённый HMAC IP-адреса для ручного анализа злоупотреблений; эти таблицы не хранят ни ключ, ни IP-адрес в исходном виде. Журналы доступа к инфраструктуре и зашифрованная очередь исходящей доставки относятся к отдельным операционным границам.
  3. OmniRoute никогда не отправляет сервису Radar запросы, ответы, разговоры, учётные данные провайдера, трафик моделей, данные о времени доступности и задержках или локальную конфигурацию провайдера.
  4. Ответ проверяется, валидируется и кэшируется локально (см. Модель безопасности). Radar имеет ровно четыре серверных сетевых пути: syncRadar() для каталога, syncRadarReferrals() для рефералов и syncRadarOffers() / syncRadarIntel() для предложений и Intel, доступных только участникам программы поддержки.

Ключ участника программы поддержки — это необязательный токен Bearer (radar_settings.supporter_key), который позволяет сервису ленты определить предоставляемый уровень доступа (см. Уровни). Он:

  • Хранится в зашифрованном виде с использованием тех же вспомогательных функций AES-256-GCM encrypt()/decrypt() (src/lib/db/encryption.ts), которые применяются для учётных данных провайдера.
  • Задаётся через POST /api/radar/settings ({ supporterKey: "omr_" + 40 шестнадцатеричных символов }) и никогда не возвращается целиком — ответ содержит замаскированную форму (omr_****abcd).
  • При изменении или удалении ключа кэши каталога, рефералов, предложений и Intel атомарно признаются недействительными. Следующая синхронизация или операция чтения определяет новые права доступа на стороне сервера; сохранение ключа само по себе не выполняет сетевой запрос и не использует одноразовый ключ активации.
  • Отправляется сервису ленты как токен Bearer при синхронизирующем GET-запросе — никакие другие сведения о ключе никогда не покидают клиент.

Правила доступа и безопасности, отображаемые до согласия

Заголовок раздела «Правила доступа и безопасности, отображаемые до согласия»

Неактивная панель управления отображает эти правила из src/app/(dashboard)/dashboard/radar/RadarAccessExplainer.tsx до выполнения любого из действий активации. Каноническая шкала доступа:

Уровень Условия получения Доступ Правило повторной выдачи/истечения срока
Сообщество Любой пользователь; ключ не требуется Полный каталог с задержкой примерно в 30 дней Доступен всегда; ключ не выдаётся
Звезда + подписка GitHub OAuth подтверждает и звезду у репозитория, и подписку на владельца Однократное чтение актуального каталога, затем уровень «Сообщество» Одна выдача за вход; повторно не выдаётся
Участник Top 10 Позиции 1–10 в последнем полном еженедельном рейтинге 365 дней доступа к актуальным данным Активируется по запросу; выход из рейтинга не сокращает уже предоставленный период
Участник Top 100 Позиции 11–100 в этом рейтинге 90 дней доступа к актуальным данным То же правило активации по запросу с идемпотентностью
Покупка уровня поддержки Разовая покупка на 6 месяцев, 1 год или пожизненно Актуальный каталог, подписанные актуальные предложения и Intel Без автоматического продления
Пожертвование/выдача вручную Пожертвование, рассмотренное владельцем, или выдача владельцем на явно указанный срок в днях/пожизненно Тот же доступ к актуальным данным на предоставленный период Аудируемая идемпотентная выдача

Слитые PR, коммиты и количество изменённых строк — только входные данные для рейтинга. Вход пользователя, не входящего в Top 100, не даёт права на доступ как участнику независимо от количества PR. Ограниченные по сроку покупки, пожертвования, периоды участника и выдачи вручную суммируются с текущим сроком действия; пожизненный доступ имеет приоритет. Изменение рейтинга никогда не отменяет задним числом и не сокращает уже предоставленный срок.

Размещённая лицензия является персональной, а отображаемое пользователю правило допускает одну активную установку одновременно. В этом выпуске не заявляется аппаратная привязка: OSS-синхронизация не создаёт отпечаток оборудования и не поддерживает криптографическую аренду устройства. В указанной выше проверенной ревизии частного сервера реализованное правоприменение включает проверку прав доступа и сигнал для ручной проверки, когда один и тот же активный ключ используется с четвёртого уникального IP-адреса в течение 24 часов. Этот сигнал никогда не блокирует и не отзывает ключ автоматически. Восстановление отзывает и заменяет потерянный ключ, сохраняя существующий срок действия; оно не запускает заново приобретённый или предоставленный период.

Актуальные предложения формируются вручную и могут изменяться или истекать. На экране согласия также точно указаны границы конфиденциальности: загружаются подписанные метаданные каталога/рефералов; действительный ключ дополнительно открывает доступ к подписанным предложениям и Intel; Bearer-ключ и стандартные метаданные подключения передаются размещённому сервису; промпты, ответы, беседы, учётные данные провайдеров, трафик моделей, время безотказной работы, задержка и локальная конфигурация провайдера не передаются.


Экран активации (/dashboard/radar) содержит ссылки на два способа получения ключа сторонника. Сам OSS-репозиторий никогда не выдаёт ключи, не выполняет платёжный код и никогда не указывает цену — цены определяются и отображаются исключительно на целевых страницах, а не в этом репозитории (проектное решение D14).

  • «Я участник проекта» — открывает RADAR_CONTRIBUTOR_CLAIM_URL (по умолчанию https://radar.omniroute.online/auth/github), процесс запроса через GitHub OAuth, размещённый на частном сервере Radar. Он проверяет последний завершённый недельный рейтинг: участники из первой десятки получают 365 дней, а занявшие места с 11-го по 100-е — 90 дней. За пределами первой сотни количество PR никогда не предоставляет доступ; вместо этого процесс проверяет отдельный одноразовый уровень за звезду и подписку.
  • «Поддержать проект» — открывает RADAR_SUPPORTER_PLANS_URL (по умолчанию https://radar.omniroute.online/planos), размещённую на сервере страницу с вариантами разовой оплаты за 6 месяцев, 1 год и бессрочный доступ. На OSS-странице по-прежнему не отображаются денежные суммы.

Оба URL разрешаются на стороне сервера (src/lib/radar/links.ts, по той же схеме переопределения через переменные окружения, что и 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. Если восстановление доступно, обработчик доставки отправляет короткоживущую одноразовую ссылку. При её открытии токен немедленно помещается во временный зашифрованный cookie 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. Получите недавно выпущенный или восстановленный ключ через заявку участника, процесс оформления/оплаты плана, процедуру восстановления либо у авторизованного оператора частного сервера. Не вставляйте необработанный ключ в журналы, снимки экрана, комментарии к задачам или аргументы командной строки.
  2. Включите флаг функции RADAR_ENABLED в локальной установке OmniRoute. Это сделает интерфейс доступным, но сетевое взаимодействие останется отключённым, пока не будет сохранено отдельное согласие.
  3. Откройте /dashboard/radar, вставьте ключ и активируйте его. Браузер отправит один локальный запрос POST /api/radar/settings с { optIn: true, supporterKey }; ключ будет зашифрован локально, а ответ будет содержать только omr_****&lt;last4&gt;.
  4. Дождитесь, пока на экране активации завершится синхронизация каталога, или выберите Синхронизировать сейчас. Убедитесь, что на странице отображаются статус live, версия ленты и время получения. Для локальной диагностики с аутентификацией запрос GET /api/radar/status сообщает о наличии согласия/ключа и состояниях четырёх кешей, не возвращая сам ключ. Запрос POST /api/radar/sync-all позволяет явно обновить каталог, реферальные данные, предложения и аналитику Intel.
  5. Откройте /dashboard/radar/setup?provider=&lt;provider&gt;. Перейдите по URL учётных данных, принадлежащему провайдеру, выберите Добавить API-ключ, сохраните данные через настоящую форму провайдера, вернитесь к руководству и выполните Проверить подключение. Руководство использует стандартные маршруты /api/providers и /api/providers/&lt;connection-id&gt;/test; оно не создаёт отдельные учётные данные Radar.
  6. Откройте /dashboard/radar/combos после активации как минимум двух совместимых подключений к провайдерам. Просмотрите предложенное семейство и создайте комбинацию через существующий API комбинаций. Предложения и аналитика Intel остаются отдельными подписанными кешами только для уровня live; их можно проверить на соответствующих страницах Radar.
  7. Перезагрузите /dashboard/radar и страницу настройки. Согласие, состояние маскированного ключа, проверенный кеш, сохранённое подключение к провайдеру и действие проверки должны сохраниться после перезагрузки. Фиксируйте подтверждение только после того, как необработанный ключ и учётные данные провайдера перестанут отображаться.

Само по себе сохранение ключа не доказывает наличие действующего права доступа к уровню live. Подтверждением служит совокупность результата GET /v1/license/check от частного сервиса, уровня live, предоставляемого OSS-каталогом, проверенного подписанного кеша и реального процесса подключения к провайдеру и его проверки. При недействительном, просроченном или отозванном ключе каталог безопасно переключается на уровень community; это не должно считаться успешной проверкой ключа уровня live.

RADAR_ADMIN_URL при необходимости добавляет пункт Администрирование Radar ↗ сразу после пользовательского пункта Radar в разделе «Расходы» боковой панели. Значение по умолчанию намеренно отсутствует: если переменная не задана или недействительна, статическая боковая панель, палитра команд и экран настройки боковой панели не содержат пункт администратора и частный URL.

Значение определяется на стороне сервера и передаётся в ответе GET /api/settings, требующем аутентификации управления, только аутентифицированному сеансу панели управления либо доверенному владельцу loopback-интерфейса во время локальной начальной настройки без входа в систему. CLI, внутренние сервисы и аутентификация по API-ключу с областью действия управления его не получают. Браузер повторно проверяет ответ перед созданием внешней ссылки, которая открывается с атрибутами noopener noreferrer.

Используйте HTTPS-URL туннеля/tailnet без учётных данных. Обычный HTTP допускается только для loopback-перенаправления SSH, например http://127.0.0.1:9351; при других схемах, встроенных учётных данных, некорректных URL и удалённых HTTP-адресах применяется безопасный отказ, а навигация остаётся неактивной.


Подпись Ed25519 точной последовательности байтов

Заголовок раздела «Подпись 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 в формате DER с кодировкой base64 или 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()) — повреждённая или вручную отредактированная строка кэша приводит к использованию базового набора данных вместо её выдачи.

syncRadar() применяет жёсткое ограничение в 10 МБ к телу ответа фида — подписанный фид представляет собой JSON-документ размером порядка нескольких КБ, поэтому всё, что превышает этот размер, указывает на неправильно настроенный или вредоносный RADAR_FEED_URL (либо на вышестоящий сервер, возвращающий мусор), а не на допустимый каталог. Ограничение применяется на двух уровнях:

  1. Предварительная проверка Content-Length полностью пропускает чтение тела, если заголовок уже указывает значение, превышающее ограничение.
  2. Проверка накапливаемого общего размера во время чтения тела обеспечивает соблюдение ограничения, даже если Content-Length отсутствует или занижает фактический размер, — одному только заголовку никогда не доверяют. Объединение накопленных фрагментов сохраняет точную последовательность байтов, необходимую для последующей проверки подписи Ed25519.

Превышение ограничения приводит к возврату { status: "too_large" } и оставляет кэш без изменений, следуя тому же неразрушающему принципу, что и при любой другой ошибке синхронизации (invalid_signature, invalid_schema, stale).


Схема фида содержит поле 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, ключом служит provider:modelId), значение этого конкретного поля из фида пропускается — приоритет имеет значение оператора.
  2. enabled: false отключает запись с указанием происхождения. Если запись фида отключает запись, в объединённом результате устанавливаются enabled: false и disabledBy: "radar", чтобы UI мог объяснить, почему запись из доступной стала отключённой.
  3. Добавленная пользователем запись, отсутствующая в фиде, сохраняется без изменений. Записи, существующие только в базовой конфигурации (или добавленные локально) и не имеющие соответствующей записи в фиде, передаются без изменений.
  4. Запись с надгробной меткой никогда не восстанавливается. Если оператор явно удалил запись (набор tombstones), повторное добавление этого provider:modelId фидом в более поздней версии не возвращает её.

Редактируемые поля и надгробные метки сохраняются в radar_local_model_state (миграция 153_radar_local_model_state.sql). Публичный адаптер БД (src/lib/db/radar.ts) преобразует эти строки в карту localOverrides и набор tombstones, используемые applyFeed(); в рабочей среде getRadarCatalog() загружает это состояние после успешного прохождения проверок флага, кеша и схемы. Оператор может редактировать только displayName и enabled. Идентификаторы провайдера/модели, происхождение данных фида, квота, возможности, условия обслуживания и данные настройки не могут быть записаны через этот интерфейс.

Панель мониторинга предоставляет четыре локальных действия:

  • Редактировать изменяет локальное отображаемое имя и состояние включения.
  • Сбросить локальные изменения очищает оба редактируемых поля, не изменяя надгробную метку.
  • Скрыть создаёт надгробную метку, чтобы последующие обновления фида не могли заново создать строку.
  • Восстановить удаляет надгробную метку; отдельно сохранённое переопределение продолжает действовать.

Значение 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 с ненулевым значением контекста/возможности должна содержать не требующий учётных данных HTTPS-адрес в metadataEvidenceUrls[]; в противном случае проверка схемы завершается ошибкой и кеш не заменяется. Таблица каталога отображает все три состояния как ✓, ✕ и ?.

Подтверждённые значения familyId сохраняются после наложения при чтении и управляют чистым модулем buildRadarComboSuggestions() (src/lib/radar/comboSuggestions.ts). Семейство предлагается только тогда, когда как минимум два разных провайдера имеют активные подключения и предоставляют модель с точным курируемым ID. Отключённые модели, неактивные провайдеры, отсутствующие ID моделей, семейства с единственным элементом и неоднозначные совпадения псевдонимов/префиксов отклоняются. Предложения используют существующую стратегию priority, располагая наибольший регулярно предоставляемый месячный бюджет первым; UI создаёт их только через POST /api/combos.

Управляемый интерфейс находится по адресу /dashboard/radar/combos. Он обращается только к локальным конечным точкам 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, которое интерфейс отображает в виде метки:

  • "baseline" — запись осталась неизменной по сравнению со статическим каталогом выпуска.
  • "radar" — одно или несколько полей были обновлены из потока.
  • "local" — оператор задал для этой записи как минимум одно локальное переопределение (локальные переопределения всегда имеют приоритет над потоком согласно правилу 1, независимо от данных потока).

Локальные интерфейсы — никогда не прокси для фида

Заголовок раздела «Локальные интерфейсы — никогда не прокси для фида»

Приведённые ниже семейства локальных маршрутов Radar обеспечивают работу пользовательского интерфейса в 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) — с помощью cookie сеанса панели управления или API-ключа с областью управления; этот же механизм защищает остальные маршруты /api/settings/*. Проверка с возвратом 404 при отключённом флаге всегда выполняется до проверки аутентификации, поэтому установка с отключённым RADAR_ENABLED остаётся побайтово идентичной (запрос аутентификации не появляется только ради того, чтобы узнать, что интерфейс не существует); после включения флага неаутентифицированный запрос получает 401 до любого чтения из БД или записи в неё. GET /api/radar/settings никогда не возвращает исходный ключ спонсора независимо от состояния аутентификации — только его замаскированную форму и логическое значение hasSupporterKey.


Предложения используют собственный подписанный артефакт, GET /v1/offers/latest, и никогда не используют общий кеш каталога или рефералов. Серверная конечная точка требует действительный активный Bearer-ключ сторонника; резервный вариант для сообщества отсутствует. Поэтому syncRadarOffers() останавливается до обращения к сети, если флаг функции выключен, оператор не дал согласие или ключ сторонника не настроен.

После успешного GET-запроса клиент проверяет подпись Ed25519 над точными байтами ответа, выполняет валидацию по RadarOffersFeedSchema, требует, чтобы и подписанное тело, и заголовок x-omniroute-feed-tier содержали значение live, проверяет, что версия в точечной нотации строго новее, и только после этого атомарно заменяет radar_offers_cache (миграция 144_radar_offers_cache.sql). Применяется то же ограничение в 10 МБ на заголовок и поток вместе, что и для других лент. При ошибках подписи, схемы, уровня, повторного воспроизведения, размера, HTTP и сети сохраняется последний проверенный кеш.

Закрытая структура предложения поддерживает три сопоставимых типа преимуществ: процент в базисных пунктах, кредит в минимальных денежных единицах или дни пробного периода. Партнёрское предложение должно включать общедоступный базовый вариант того же типа, а его преимущество должно быть строго больше; у официальных предложений партнёрского базового варианта нет. URL-адреса должны использовать HTTPS и не содержать учётных данных. getRadarOffers() в целях безопасности повторно валидирует кешированную полезную нагрузку и фильтрует истёкшие записи при каждом локальном чтении; /dashboard/radar/offers повторно фильтрует их по сроку действия перед отображением, использует текст на португальском языке, если он доступен, с резервным вариантом на английском и явно помечает партнёрские предложения.

Браузер обращается только к локальным маршрутам: он считывает замаскированный снимок настроек, запрашивает у POST /api/radar/offers/sync обновление на стороне сервера, а затем считывает GET /api/radar/offers. При отсутствии ключа вместо попытки запроса ленты отображаются существующие ссылки для участников и поддержки. Внешние ссылки на предложения открываются в новой вкладке с noopener noreferrer. В этом выпуске MCP-инструмент radar_offers недоступен.


Intel представляет собой подписанный артефакт по адресу GET /v1/intel/latest. Закрытая схема RadarIntelFeedSchema принимает только принадлежащие Radar рейтинги ELO, рассчитанные частным куратором на основе подтверждённых сравнений, а также фактические изменения возраста и количества элементов каталога, полученные из подписанных снимков каталога. Методика зафиксирована: начальный рейтинг — 1000, K=32. Пустой рейтинг допустим, если ни одно сравнение не было подтверждено; клиент никогда не создаёт его искусственно.

syncRadarIntel() применяет те же механизмы, что и для предложений: серверный Bearer-ключ, 30-секундный тайм-аут, ограничение потоковых данных в 10 МиБ, проверку Ed25519 над точными байтами, строгую проверку схемы, требование значения live в теле и заголовке, минимально допустимую версию и сохранение последнего корректного кеша. После сохранения проверенного активного снимка клиент формирует radar:<sha256(supporter key)>, хранит только этот односторонний идентификатор и создаёт специальное событие признания radar_supporter. Соответствующий значок radar-supporter выдаётся идемпотентно и не начисляет XP; он никогда не обновляет таблицы лидеров и не использует повторно token_share. /dashboard/radar/intel отображает значок только на основе проверенных метаданных локального кеша.

CLI предоставляет команды omniroute radar status и omniroute radar sync. Обе взаимодействуют только с локальным API OmniRoute. Команда status выполняет доступный только для чтения запрос GET /api/radar/status; sync отправляет один запрос POST /api/radar/sync-all и выводит результат для каждой ленты. Ни одна из команд не считывает, не принимает и не выводит ключ сторонника, а также не обращается напрямую к сервису Radar.


Реферальные ссылки предоставляются из автономного, всегда актуального фида — GET /v1/referrals/latest — отдельно от фида каталога. Это сделано намеренно: фид каталога на тарифе community представляет собой снимок, который может быть устаревшим до 30 дней, поэтому извлечённая из него реферальная ссылка раньше отставала от реального списка ссылок на сервере на такой же срок (вновь добавленная реферальная ссылка могла не попасть к пользователю бесплатного тарифа/community в течение месяца). Фид реферальных ссылок устраняет эту задержку, синхронизируясь независимо и гораздо чаще.

// Тело ответа GET /v1/referrals/latest (подписано Ed25519, тот же закреплённый ключ,
// что и для фида каталога):
{
feed: "omniroute-radar-referrals",
schemaVersion: 1,
generatedAt: string, // ISO — детерминировано: max(updatedAt) по всем реферальным
// ссылкам, поэтому два идентичных запроса дают в точности
// одинаковые подписанные байты/подпись
referrals: {
fixed: RadarReferral[], // присутствует на КАЖДОМ тарифе, включая запросы без аутентификации/community
campaigns: RadarReferral[], // заполняется только при наличии действующего активного (supporter) Bearer-
// ключа; запросы без аутентификации/с истёкшим ключом получают []
},
}
// 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 (с теми же переопределениями RADAR_FEED_URL/RADAR_FEED_PUBKEY для форков, что и каталог), проверяет подпись Ed25519 для точных байтов ответа (verifyFeedBytes), валидирует по RadarReferralsFeedSchema и кеширует в таблицу radar_referrals_cache (миграция 142_radar_referrals_cache.sql) — таблицу, полностью отдельную от каталожной radar_feed_cache. Ограничение ответа в 10 МБ и нижняя граница generatedAt отклоняют входящий фид, если он старше закешированного, защищая от повторного воспроизведения старого подписанного артефакта. Одинаковая временная метка допускается: сервер намеренно присваивает вариантам реферальных ссылок для community и live одинаковое детерминированное значение generatedAt, поэтому подписанная полезная нагрузка и предоставляемый тариф могут измениться после смены supporter-ключа без изменения самого набора ссылок. Никогда не выбрасывает исключения — всегда возвращает объект состояния; поле reason ошибок никогда не содержит трассировку стека.

Кеш реферальных ссылок поддерживается актуальным двумя триггерами, оба из которых не зависят от собственного 24-часового интервала каталога:

  • Синхронизация при чтении — GET /api/radar/referrals самостоятельно вызывает syncRadarReferrals() непосредственно перед отправкой ответа, если кеш отсутствует или старше REFERRALS_STALE_MS (1 ч, shouldSyncReferralsOnRead()). Благодаря этому фиксированные ссылки «всегда актуальны» уже при следующей загрузке панели управления, без ожидания какого-либо фонового таймера.
  • Побочная синхронизация планировщика — radarSchedulerTick() (scheduler.ts) независимо проверяет устаревание реферальных ссылок во время того же ежечасного тика, который используется для каталога, и при необходимости вызывает 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 для этого провайдера либо null. Просматривает только fixed — кампания никогда не используется как ссылка провайдера «по умолчанию».

Фактическое правило определения «какая реферальная ссылка является ссылкой по умолчанию для провайдера» находится в findDefaultReferral() (src/lib/radar/referrals.ts) — небольшой чистой функции без импорта БД, которую безопасно импортировать в компонент с "use client". getRadarReferrals/ getDefaultReferralFor (в index.ts) подключают @/lib/db/radar и поэтому остаются доступными только на сервере; панель управления провайдерами импортирует referrals.ts напрямую вместо index.ts (см. ниже), чтобы не включать better-sqlite3 в браузерный бандл.

Следует точно такому же порядку проверок, как и все остальные маршруты Radar: если RADAR_ENABLED отключён → 404 (проверяется первым, поведение побайтно идентично прежнему); без аутентификации → 401; в остальных случаях при устаревших данных запускается синхронизация при чтении (см. выше), после чего возвращается 200 с { fixed, campaigns, tier } — tier берётся непосредственно из строки кеша (возможно, только что обновлённой) и носит исключительно информационный характер (определяет приведённый ниже текст ненавязчивого предложения перейти на платный тариф в UI). Маршрут никогда не проксирует сервер фида напрямую — в его исходном коде нет вызова fetch(; сетевое обращение всегда происходит только внутри syncRadarReferrals(), в соответствии с тем же принципом использования исключительно локального кеша, что и в /api/radar/catalog.

UI панели управления — вкладка «Бесплатные кредиты» на /dashboard/radar

Заголовок раздела «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.
  • Если 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() при ответе 404 от /v1/referrals/latest корректно возвращает { status: "error" }, а кеш просто остаётся пустым, поэтому 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 сохраняется последний проверенный локальный снимок.



Исходный код OmniRoute (a58000c7685f)

HagiCode

HagiCode — агентная среда разработки со структурированными процессами, параллельным выполнением несколькими агентами и интерфейсами Hero Dungeon.

Превращайте идеи в полезное ПО с более умным, быстрым и увлекательным агентным рабочим процессом.

Главный экран HagiCode в светлой теме
  • SmartСтруктурированные процессы превращают намерение в исполнимый путь от идеи до готового изменения.
  • EfficientМультиагентные процессы параллельно продвигают исследование, реализацию и проверку.
  • FunHero Dungeon делает длительную совместную разработку наглядной и увлекательной.
Перейти на HagiCode