Radar Free-Model Catalog (Русский)
Статус поставки в v3.8.51
Заголовок раздела «Статус поставки в v3.8.51»Приведённый ниже статус разграничивает возможности, реализованные в этом 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_ENABLED (по умолчанию отключён)
Заголовок раздела «Флаг: 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" } — сетевой вызов не выполняетсяКогда включены оба параметра, процесс синхронизации выглядит следующим образом:
GET <базовый URL ленты>/v1/catalog/latestсx-omniroute-radar-schema: 2и необязательным заголовкомAuthorization: Bearer <ключ участника программы поддержки>(см. ниже). При отсутствии заголовка схемы серверы по умолчанию предоставляют отдельно подписанный переходный артефакт v1, поэтому ранее установленные клиенты продолжают получать обновления.- Это поток приложения, предназначенный только для скачивания, однако он всё равно представляет собой HTTPS-запрос. Размещённая инфраструктура получает обычные метаданные подключения, например исходный IP-адрес. Если настроен ключ участника программы поддержки, синхронизация также отправляет этот ключ в заголовке Bearer, чтобы сервис мог определить права доступа. В точной ревизии частного сервера, указанной выше в описании границ доказательной базы, для учёта запросов к ленте используются хэши ключей, агрегированные данные об использовании и ежедневно изменяемый усечённый HMAC IP-адреса для ручного анализа злоупотреблений; эти таблицы не хранят ни ключ, ни IP-адрес в исходном виде. Журналы доступа к инфраструктуре и зашифрованная очередь исходящей доставки относятся к отдельным операционным границам.
- OmniRoute никогда не отправляет сервису Radar запросы, ответы, разговоры, учётные данные провайдера, трафик моделей, данные о времени доступности и задержках или локальную конфигурацию провайдера.
- Ответ проверяется, валидируется и кэшируется локально (см.
Модель безопасности). 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-клиента, поскольку локальная установка никогда не получает адрес электронной почты покупателя или участника проекта и не может восстановить исходный ключ из зашифрованных настроек.
- Отправьте адрес электронной почты, связанный с ключом. Независимо от наличия восстанавливаемой лицензии сервис возвращает одну и ту же страницу подтверждения, поэтому форма не позволяет определить существование учётных записей.
- Если восстановление доступно, обработчик доставки отправляет короткоживущую одноразовую ссылку. При её открытии токен немедленно помещается во временный зашифрованный cookie
HttpOnly/Secure, после чего выполняется перенаправление на чистый URL/recover; страница не содержит токен, адрес электронной почты, старый ключ или ключ замены. - Подтвердите отзыв. Частный сервис отзывает предыдущий ключ, создаёт замену с теми же тарифом и сроком действия и ставит её в очередь для отправки по электронной почте в рамках одной транзакции. Новый ключ никогда не возвращается браузеру.
- Вставьте новый ключ в
/dashboard/radar. Старый ключ теперь должен перейти в состояниеcommunity, а новый — обеспечить проверенную синхронизациюlive. Повторное открытие той же ссылки восстановления должно завершиться общей ошибкой о недействительной или истёкшей ссылке.
Маршрут восстановления и обработчик почтовой доставки размещённого сервиса могут присутствовать в коде, но при этом быть недоступными в конкретном развёртывании. Не считайте процесс готовым к эксплуатации, пока сервер не будет развёрнут, поставщик доставки не будет настроен с контролируемым получателем, а полная цепочка с одноразовой ссылкой не будет протестирована.
После получения ключа (omr_ + 40 шестнадцатеричных символов) основным способом активации на экране (src/app/(dashboard)/dashboard/radar/page.tsx) становится поле для вставки ключа: вставка ключа и отправка формы выполняют POST /api/radar/settings ({ optIn: true, supporterKey }) одним запросом — вставка ключа одновременно сохраняет его и включает участие, разблокируя экран. Формат (omr_ + 40 шестнадцатеричных символов) сначала проверяется на стороне клиента с помощью общего вспомогательного метода isValidSupporterKeyFormat() (src/lib/radar/supporterKey.ts) для удобства пользователя; в любом случае окончательной является проверка схемой Zod на стороне сервера. После установки ключа экран активации вместо пустого поля отображает замаскированное значение (supporterKeyMasked из GET /api/radar/settings) и элемент управления «изменить ключ» для вставки нового — исходный ключ никогда не отображается повторно. Две расположенные выше кнопки запроса и выбора тарифа остаются способом получить ключ; это поле предназначено для его активации оператором, у которого ключ уже есть.
Сквозная активация и пошаговая настройка
Заголовок раздела «Сквозная активация и пошаговая настройка»Между частным сервисом ленты и этим OSS-клиентом намеренно установлена узкая граница ответственности: сервис выдаёт и проверяет ключ сторонника, а локальная установка OmniRoute шифрует ключ, синхронизирует подписанные артефакты на стороне сервера и помогает настроить поставщика. Порядок сопровождаемой проверки:
- Получите недавно выпущенный или восстановленный ключ через заявку участника, процесс оформления/оплаты плана, процедуру восстановления либо у авторизованного оператора частного сервера. Не вставляйте необработанный ключ в журналы, снимки экрана, комментарии к задачам или аргументы командной строки.
- Включите флаг функции
RADAR_ENABLEDв локальной установке OmniRoute. Это сделает интерфейс доступным, но сетевое взаимодействие останется отключённым, пока не будет сохранено отдельное согласие. - Откройте
/dashboard/radar, вставьте ключ и активируйте его. Браузер отправит один локальный запросPOST /api/radar/settingsс{ optIn: true, supporterKey }; ключ будет зашифрован локально, а ответ будет содержать толькоomr_****<last4>. - Дождитесь, пока на экране активации завершится синхронизация каталога, или выберите Синхронизировать сейчас. Убедитесь, что на странице отображаются статус
live, версия ленты и время получения. Для локальной диагностики с аутентификацией запросGET /api/radar/statusсообщает о наличии согласия/ключа и состояниях четырёх кешей, не возвращая сам ключ. ЗапросPOST /api/radar/sync-allпозволяет явно обновить каталог, реферальные данные, предложения и аналитику Intel. - Откройте
/dashboard/radar/setup?provider=<provider>. Перейдите по URL учётных данных, принадлежащему провайдеру, выберите Добавить API-ключ, сохраните данные через настоящую форму провайдера, вернитесь к руководству и выполните Проверить подключение. Руководство использует стандартные маршруты/api/providersи/api/providers/<connection-id>/test; оно не создаёт отдельные учётные данные Radar. - Откройте
/dashboard/radar/combosпосле активации как минимум двух совместимых подключений к провайдерам. Просмотрите предложенное семейство и создайте комбинацию через существующий API комбинаций. Предложения и аналитика Intel остаются отдельными подписанными кешами только для уровняlive; их можно проверить на соответствующих страницах Radar. - Перезагрузите
/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()) — повреждённая или вручную
отредактированная строка кэша приводит к использованию базового набора данных вместо
её выдачи.
Ограничение размера ответа (10 МБ)
Заголовок раздела «Ограничение размера ответа (10 МБ)»syncRadar() применяет жёсткое ограничение в 10 МБ к телу ответа фида —
подписанный фид представляет собой JSON-документ размером порядка нескольких КБ,
поэтому всё, что превышает этот размер, указывает на неправильно настроенный или
вредоносный RADAR_FEED_URL (либо на вышестоящий сервер, возвращающий мусор), а не
на допустимый каталог. Ограничение применяется на двух уровнях:
- Предварительная проверка
Content-Lengthполностью пропускает чтение тела, если заголовок уже указывает значение, превышающее ограничение. - Проверка накапливаемого общего размера во время чтения тела обеспечивает
соблюдение ограничения, даже если
Content-Lengthотсутствует или занижает фактический размер, — одному только заголовку никогда не доверяют. Объединение накопленных фрагментов сохраняет точную последовательность байтов, необходимую для последующей проверки подписи Ed25519.
Превышение ограничения приводит к возврату { status: "too_large" } и оставляет
кэш без изменений, следуя тому же неразрушающему принципу, что и при любой другой
ошибке синхронизации (invalid_signature, invalid_schema, stale).
Уровни: community и live
Заголовок раздела «Уровни: community и live»Схема фида содержит поле tier: "community" | "live", значение которого определяется на стороне сервера
сервисом фида на основании запроса (наличия и действительности ключа спонсора)
— клиент никогда не определяет свой уровень самостоятельно.
community— бесплатный каталог, отстающий от самых свежих данных примерно на 30 дней. Его получает запрос без аутентификации или с недействительным ключом.live— самый свежий каталог, предоставляемый запросам с действительным ключом спонсора.
При недействительном или просроченном ключе спонсора используется community — это никогда не считается
ошибкой. Путь синхронизации отличает только сбои подписи/схемы/версии (все
восстановимы и не влияют критически на кешированное состояние) от успешного результата { status: "updated", version, tier }. Клиенту не требуется обрабатывать отдельный путь ошибок
для каждого уровня.
Предоставляемый уровень определяется заголовком ответа, а не подписанным телом
Заголовок раздела «Предоставляемый уровень определяется заголовком ответа, а не подписанным телом»Поле tier в теле подписанного фида всегда имеет значение "live" — сервис фида публикует
два подписанных артефакта для каждой версии: live включает текущие кампании, а community
их не содержит. Каждый артефакт подписывается на основе собственных точных байтов. При этом тело
не используется для принятия решения о правах доступа; фактически выбранный для запроса уровень передаётся
в заголовке ответа x-omniroute-feed-tier и определяется на стороне сервера по ключу
Authorization из запроса.
syncRadar() (src/lib/radar/sync.ts::parseServedTierHeader()) — единственное место,
где определяется уровень, которому должен доверять клиент:
- Разобрать
x-omniroute-feed-tierс помощьюRadarTierSchema(Zod) — отсутствующий заголовок или значение, не совпадающее в точности с"community"или"live", считается отсутствующим (оно никогда не сохраняется в кеш/UI как есть; это также обеспечивает совместимость со старыми серверами фида, появившимися до введения этого заголовка). - Использовать поле
tierподписанного тела (всегда"live") только как резервный вариант, если шаг 1 не дал результата. - Определённый уровень кешируется и возвращается как
{ status: "updated", version, tier }— именно это значение отображается на панели мониторинга, а не необработанное поле тела.
Правила объединения наложений при чтении
Заголовок раздела «Правила объединения наложений при чтении»applyFeed() (src/lib/radar/applyFeed.ts) накладывает кешированный фид поверх
статической базовой конфигурации во время чтения, внутри getRadarCatalog(). Базовый массив
(FREE_MODEL_BUDGETS) никогда не изменяется — при каждом вызове заново вычисляется MergedEntry[].
Четыре правила в порядке приоритета:
- Фид никогда не перезаписывает локальное переопределение. Для каждого поля: если оператор
изменил поле записи (
localOverrides, ключом служитprovider:modelId), значение этого конкретного поля из фида пропускается — приоритет имеет значение оператора. enabled: falseотключает запись с указанием происхождения. Если запись фида отключает запись, в объединённом результате устанавливаютсяenabled: falseиdisabledBy: "radar", чтобы UI мог объяснить, почему запись из доступной стала отключённой.- Добавленная пользователем запись, отсутствующая в фиде, сохраняется без изменений. Записи, существующие только в базовой конфигурации (или добавленные локально) и не имеющие соответствующей записи в фиде, передаются без изменений.
- Запись с надгробной меткой никогда не восстанавливается. Если оператор явно удалил
запись (набор
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[]; в противном случае проверка схемы завершается ошибкой и кеш
не заменяется. Таблица каталога отображает все три состояния как ✓, ✕ и ?.
Рекомендованные комбинации и доступ MCP
Заголовок раздела «Рекомендованные комбинации и доступ MCP»Подтверждённые значения 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 недоступен.
Radar Intel, значок сторонника и CLI
Заголовок раздела «Radar Intel, значок сторонника и CLI»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 в браузерный бандл.
GET /api/radar/referrals
Заголовок раздела «GET /api/radar/referrals»Следует точно такому же порядку проверок, как и все остальные маршруты 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), — отдельное новое визуальное оформление никогда не применяется.
Как самостоятельно разместить фид
Заголовок раздела «Как самостоятельно разместить фид»Форк или пользователь, самостоятельно размещающий сервис и желающий получить полный контроль над каталогом, может запустить собственный сервис фида без внесения изменений в клиентский код:
- Реализуйте эндпоинт
GET /v1/catalog/latest, возвращающий тело JSON, которое соответствуетRadarFeedSchema(src/lib/radar/feedSchema.ts), — верхнеуровневые поляfeed: "omniroute-radar",schemaVersion: 2,version,tier,providers,models,quirksиtotals. Учитывайтеx-omniroute-radar-schema: 2; сервер, совместимый с переходным периодом, должен по умолчанию направлять запросы без этого заголовка на отдельно подписанный артефакт v1. - Подпишите точные байты ответа с помощью пары ключей Ed25519 и верните подпись в формате base64
в заголовке ответа
x-omniroute-feed-signature. - Задайте для
RADAR_FEED_URLновый базовый URL, а дляRADAR_FEED_PUBKEY— соответствующий открытый ключ (SPKI в формате base64-DER или PEM); см. справочник по переменным окружения. - Включите
RADAR_ENABLEDи явно подтвердите участие черезPOST /api/radar/settings({ optIn: true }).
Другие изменения кода не требуются — verifyFeedBytes() автоматически использует
переопределение (getFeedPublicKeys() в src/lib/radar/pinnedKeys.ts), а сравнение
версий, проверка схемы и правила слияния применяются к самостоятельно размещённому
фиду без изменений.
Реферальные ссылки (см. раздел Реферальные ссылки (бесплатные кредиты)
выше) являются отдельным необязательным артефактом: форк, обслуживающий только /v1/catalog/latest,
продолжает работать в полном объёме — syncRadarReferrals() при ответе 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 сохраняется последний проверенный
локальный снимок.
Связанная документация
Заголовок раздела «Связанная документация»docs/security/ERROR_SANITIZATION.md— шаблон ответов с ошибками, которому следуют маршруты/api/radar/*.docs/reference/ENVIRONMENT.md— справочник поRADAR_FEED_URL/RADAR_FEED_PUBKEY.
HagiCode
HagiCode — агентная среда разработки со структурированными процессами, параллельным выполнением несколькими агентами и интерфейсами Hero Dungeon.
Превращайте идеи в полезное ПО с более умным, быстрым и увлекательным агентным рабочим процессом.

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