feat(radar): sync signed Intel insights

This commit is contained in:
diegosouzapw
2026-08-09 07:06:41 -03:00
parent 12051f7edd
commit d468ff4153
16 changed files with 1174 additions and 65 deletions

View File

@@ -0,0 +1,42 @@
/** GET the verified local Radar Intel cache. Never proxies the private service. */
import { NextResponse } from "next/server";
import { buildErrorBody, sanitizeErrorMessage } from "@omniroute/open-sse/utils/error";
import { getRadarIntel } from "@/lib/radar";
import { isAuthenticated } from "@/shared/utils/apiAuth";
import { CORS_HEADERS, handleCorsOptions } from "@/shared/utils/cors";
import { isFeatureFlagEnabled } from "@/shared/utils/featureFlags";
export const dynamic = "force-dynamic";
export const revalidate = 0;
export async function OPTIONS() {
return handleCorsOptions();
}
export async function GET(request: Request) {
if (!isFeatureFlagEnabled("RADAR_ENABLED")) {
return NextResponse.json(buildErrorBody(404, "Not found"), {
status: 404,
headers: CORS_HEADERS,
});
}
if (!(await isAuthenticated(request))) {
return NextResponse.json(buildErrorBody(401, "Unauthorized"), {
status: 401,
headers: CORS_HEADERS,
});
}
try {
return NextResponse.json(getRadarIntel(), {
headers: { ...CORS_HEADERS, "Cache-Control": "no-store" },
});
} catch (error: unknown) {
return NextResponse.json(
buildErrorBody(500, sanitizeErrorMessage(error) || "Failed to load Radar Intel"),
{ status: 500, headers: CORS_HEADERS }
);
}
}

View File

@@ -0,0 +1,56 @@
/** POST a server-side Radar Intel sync. The browser never receives the supporter key. */
import { NextResponse } from "next/server";
import { z } from "zod";
import { buildErrorBody, sanitizeErrorMessage } from "@omniroute/open-sse/utils/error";
import { syncRadarIntel } from "@/lib/radar/intelSync";
import { isAuthenticated } from "@/shared/utils/apiAuth";
import { CORS_HEADERS, handleCorsOptions } from "@/shared/utils/cors";
import { isFeatureFlagEnabled } from "@/shared/utils/featureFlags";
export const dynamic = "force-dynamic";
export const revalidate = 0;
const SyncBodySchema = z.object({}).strict().optional();
export async function OPTIONS() {
return handleCorsOptions();
}
export async function POST(request: Request) {
if (!isFeatureFlagEnabled("RADAR_ENABLED")) {
return NextResponse.json(buildErrorBody(404, "Not found"), {
status: 404,
headers: CORS_HEADERS,
});
}
if (!(await isAuthenticated(request))) {
return NextResponse.json(buildErrorBody(401, "Unauthorized"), {
status: 401,
headers: CORS_HEADERS,
});
}
let body: unknown;
try {
body = await request.json();
} catch {
body = undefined;
}
if (!SyncBodySchema.safeParse(body).success) {
return NextResponse.json(buildErrorBody(400, "Invalid request body"), {
status: 400,
headers: CORS_HEADERS,
});
}
try {
return NextResponse.json(await syncRadarIntel(), { headers: CORS_HEADERS });
} catch (error: unknown) {
return NextResponse.json(
buildErrorBody(500, sanitizeErrorMessage(error) || "Radar Intel sync failed"),
{ status: 500, headers: CORS_HEADERS }
);
}
}

View File

@@ -0,0 +1,70 @@
/** Read-only aggregate status of local Radar state. */
import { NextResponse } from "next/server";
import { buildErrorBody, sanitizeErrorMessage } from "@omniroute/open-sse/utils/error";
import {
getRadarCache,
getRadarIntelCache,
getRadarOffersCache,
getRadarReferralsCache,
getRadarSettings,
} from "@/lib/db/radar";
import { isAuthenticated } from "@/shared/utils/apiAuth";
import { CORS_HEADERS, handleCorsOptions } from "@/shared/utils/cors";
import { isFeatureFlagEnabled } from "@/shared/utils/featureFlags";
export const dynamic = "force-dynamic";
export const revalidate = 0;
export async function OPTIONS() {
return handleCorsOptions();
}
function cacheStatus(
cache: { version?: string; generatedAt?: string; tier: string; fetchedAt: string } | null
) {
if (!cache) return { available: false };
return {
available: true,
version: cache.version ?? cache.generatedAt,
tier: cache.tier,
fetchedAt: cache.fetchedAt,
};
}
export async function GET(request: Request) {
if (!isFeatureFlagEnabled("RADAR_ENABLED")) {
return NextResponse.json(buildErrorBody(404, "Not found"), {
status: 404,
headers: CORS_HEADERS,
});
}
if (!(await isAuthenticated(request))) {
return NextResponse.json(buildErrorBody(401, "Unauthorized"), {
status: 401,
headers: CORS_HEADERS,
});
}
try {
const settings = getRadarSettings();
return NextResponse.json(
{
settings: { optIn: settings.optIn, hasSupporterKey: settings.supporterKey !== null },
feeds: {
catalog: cacheStatus(getRadarCache()),
referrals: cacheStatus(getRadarReferralsCache()),
offers: cacheStatus(getRadarOffersCache()),
intel: cacheStatus(getRadarIntelCache()),
},
},
{ headers: { ...CORS_HEADERS, "Cache-Control": "no-store" } }
);
} catch (error: unknown) {
return NextResponse.json(
buildErrorBody(500, sanitizeErrorMessage(error) || "Failed to load Radar status"),
{ status: 500, headers: CORS_HEADERS }
);
}
}

View File

@@ -0,0 +1,47 @@
/** Aggregate local trigger for every Radar feed sync. */
import { NextResponse } from "next/server";
import { buildErrorBody, sanitizeErrorMessage } from "@omniroute/open-sse/utils/error";
import { syncRadarIntel } from "@/lib/radar/intelSync";
import { syncRadarOffers } from "@/lib/radar/offersSync";
import { syncRadarReferrals } from "@/lib/radar/referralsSync";
import { syncRadar } from "@/lib/radar/sync";
import { isAuthenticated } from "@/shared/utils/apiAuth";
import { CORS_HEADERS, handleCorsOptions } from "@/shared/utils/cors";
import { isFeatureFlagEnabled } from "@/shared/utils/featureFlags";
export const dynamic = "force-dynamic";
export const revalidate = 0;
export async function OPTIONS() {
return handleCorsOptions();
}
export async function POST(request: Request) {
if (!isFeatureFlagEnabled("RADAR_ENABLED")) {
return NextResponse.json(buildErrorBody(404, "Not found"), {
status: 404,
headers: CORS_HEADERS,
});
}
if (!(await isAuthenticated(request))) {
return NextResponse.json(buildErrorBody(401, "Unauthorized"), {
status: 401,
headers: CORS_HEADERS,
});
}
try {
const catalog = await syncRadar();
const referrals = await syncRadarReferrals();
const offers = await syncRadarOffers();
const intel = await syncRadarIntel();
return NextResponse.json({ catalog, referrals, offers, intel }, { headers: CORS_HEADERS });
} catch (error: unknown) {
return NextResponse.json(
buildErrorBody(500, sanitizeErrorMessage(error) || "Radar aggregate sync failed"),
{ status: 500, headers: CORS_HEADERS }
);
}
}

View File

@@ -0,0 +1,11 @@
-- Signed, live-only Radar Intel feed cache. The supporter identity is a
-- one-way SHA-256 marker (`radar:<64 hex>`) and never contains the raw key.
CREATE TABLE IF NOT EXISTS radar_intel_cache (
id INTEGER PRIMARY KEY CHECK (id = 1),
version TEXT NOT NULL,
tier TEXT NOT NULL CHECK (tier = 'live'),
payload TEXT NOT NULL,
signature TEXT NOT NULL,
supporter_identity TEXT NOT NULL,
fetched_at TEXT NOT NULL DEFAULT (datetime('now'))
);

View File

@@ -20,6 +20,10 @@
* Tables (migration 144):
* - radar_offers_cache: single-row signed live offers feed cache.
*
* Tables (migration 145):
* - radar_intel_cache: single-row signed live Intel feed cache plus a
* one-way supporter identity used for local recognition.
*
* The supporter key is encrypted at rest with AES-256-GCM using the same
* `encrypt()`/`decrypt()` helpers from `./encryption.ts` that protect
* provider connection credentials.
@@ -62,6 +66,15 @@ export interface RadarOffersCache {
fetchedAt: string;
}
export interface RadarIntelCache {
version: string;
tier: "live";
payload: string;
signature: string;
supporterIdentity: string;
fetchedAt: string;
}
export interface RadarLocalModelState {
provider: string;
modelId: string;
@@ -180,6 +193,7 @@ export function setRadarKey(key: string | null): void {
const clearCatalogCache = db.prepare("DELETE FROM radar_feed_cache WHERE id = 1");
const clearReferralsCache = db.prepare("DELETE FROM radar_referrals_cache WHERE id = 1");
const clearOffersCache = db.prepare("DELETE FROM radar_offers_cache WHERE id = 1");
const clearIntelCache = db.prepare("DELETE FROM radar_intel_cache WHERE id = 1");
db.transaction(() => {
updateKey.run(encrypted);
@@ -189,6 +203,7 @@ export function setRadarKey(key: string | null): void {
clearCatalogCache.run();
clearReferralsCache.run();
clearOffersCache.run();
clearIntelCache.run();
})();
}
@@ -276,6 +291,52 @@ export function setRadarOffersCache(entry: {
.run(entry.version, entry.tier, entry.payload, entry.signature, fetchedAt);
}
// ---------------------------------------------------------------------------
// radar_intel_cache
// ---------------------------------------------------------------------------
export function getRadarIntelCache(): RadarIntelCache | null {
const row = getDbInstance()
.prepare(
"SELECT version, tier, payload, signature, supporter_identity AS supporterIdentity, " +
"fetched_at AS fetchedAt FROM radar_intel_cache WHERE id = 1"
)
.get() as RadarIntelCache | undefined;
return row ?? null;
}
export function setRadarIntelCache(entry: {
version: string;
tier: "live";
payload: string;
signature: string;
supporterIdentity: string;
fetchedAt?: string;
}): void {
const fetchedAt = entry.fetchedAt ?? new Date().toISOString();
getDbInstance()
.prepare(
`INSERT INTO radar_intel_cache
(id, version, tier, payload, signature, supporter_identity, fetched_at)
VALUES (1, ?, ?, ?, ?, ?, ?)
ON CONFLICT(id) DO UPDATE SET
version = excluded.version,
tier = excluded.tier,
payload = excluded.payload,
signature = excluded.signature,
supporter_identity = excluded.supporter_identity,
fetched_at = excluded.fetched_at`
)
.run(
entry.version,
entry.tier,
entry.payload,
entry.signature,
entry.supporterIdentity,
fetchedAt
);
}
// ---------------------------------------------------------------------------
// radar_local_model_state
// ---------------------------------------------------------------------------

View File

@@ -822,6 +822,8 @@ export {
setRadarReferralsCache,
getRadarOffersCache,
setRadarOffersCache,
getRadarIntelCache,
setRadarIntelCache,
listRadarLocalModelState,
setRadarLocalModelOverride,
clearRadarLocalModelOverride,
@@ -833,6 +835,7 @@ export type {
RadarSettings,
RadarReferralsCache,
RadarOffersCache,
RadarIntelCache,
RadarLocalModelState,
RadarLocalModelOverridePatch,
RadarLocalMergeState,

View File

@@ -17,6 +17,7 @@ import {
RadarOffersFeedSchema,
type RadarOffer,
} from "./offersFeedSchema";
import { RadarIntelFeedSchema, type RadarIntelFeed } from "./intelFeedSchema";
import { applyFeed, type MergedEntry, type FeedModel } from "./applyFeed";
import { findDefaultReferral } from "./referrals";
import { isFeatureFlagEnabled } from "@/shared/utils/featureFlags";
@@ -24,6 +25,7 @@ import {
getRadarCache,
getRadarLocalMergeState,
getRadarOffersCache,
getRadarIntelCache,
getRadarReferralsCache,
type RadarLocalMergeState,
} from "@/lib/db/radar";
@@ -261,8 +263,51 @@ export function getRadarOffers(deps: GetRadarOffersDeps = {}): RadarOffersResult
}
}
export interface RadarIntelResult {
intel: RadarIntelFeed | null;
meta: {
version: string;
tier: "live";
fetchedAt: string;
supporterVerified: true;
} | null;
}
export interface GetRadarIntelDeps {
getFlag?: (key: string) => boolean;
getCache?: typeof getRadarIntelCache;
}
const EMPTY_INTEL: RadarIntelResult = { intel: null, meta: null };
/** Return only a defensively revalidated live Intel cache. */
export function getRadarIntel(deps: GetRadarIntelDeps = {}): RadarIntelResult {
const { getFlag = isFeatureFlagEnabled, getCache: getCacheFn = getRadarIntelCache } = deps;
if (!getFlag("RADAR_ENABLED")) return EMPTY_INTEL;
const cache = getCacheFn();
if (!cache || cache.tier !== "live" || !/^radar:[a-f0-9]{64}$/.test(cache.supporterIdentity)) {
return EMPTY_INTEL;
}
try {
const feed = RadarIntelFeedSchema.parse(JSON.parse(cache.payload));
if (feed.version !== cache.version || feed.tier !== "live") return EMPTY_INTEL;
return {
intel: feed,
meta: {
version: cache.version,
tier: "live",
fetchedAt: cache.fetchedAt,
supporterVerified: true,
},
};
} catch {
return EMPTY_INTEL;
}
}
// Re-export merge types for convenience
export { applyFeed, type MergedEntry, type FeedModel } from "./applyFeed";
export { findDefaultReferral } from "./referrals";
export type { RadarReferral } from "./feedSchema";
export type { RadarOffer, RadarOfferBenefit, RadarOfferLocalizedText } from "./offersFeedSchema";
export type { RadarIntelFeed, RadarIntelRanking, RadarIntelCatalog } from "./intelFeedSchema";

View File

@@ -0,0 +1,73 @@
/** Closed client mirror of the private Radar Intel feed contract. */
import { z } from "zod";
const ID_PATTERN = /^[a-z0-9][a-z0-9._-]{0,119}$/;
const CATEGORY_PATTERN = /^[a-z0-9][a-z0-9._-]{0,79}$/;
const MODEL_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,239}$/;
export const RadarIntelRankingSchema = z
.object({
rank: z.number().int().positive(),
provider: z.string().regex(ID_PATTERN),
modelId: z.string().regex(MODEL_ID_PATTERN),
category: z.string().regex(CATEGORY_PATTERN),
rating: z.number().int(),
matches: z.number().int().nonnegative(),
wins: z.number().int().nonnegative(),
losses: z.number().int().nonnegative(),
draws: z.number().int().nonnegative(),
})
.strict()
.superRefine((ranking, ctx) => {
if (ranking.matches !== ranking.wins + ranking.losses + ranking.draws) {
ctx.addIssue({ code: "custom", path: ["matches"], message: "match counters disagree" });
}
});
const CatalogDeltaSchema = z
.object({
current: z.number().int().nonnegative(),
added: z.number().int().nonnegative(),
removed: z.number().int().nonnegative(),
})
.strict();
export const RadarIntelCatalogSchema = z
.object({
currentVersion: z.string().regex(/^\d{4}\.\d{2}\.\d{2}\.\d+$/),
previousVersion: z
.string()
.regex(/^\d{4}\.\d{2}\.\d{2}\.\d+$/)
.nullable(),
currentGeneratedAt: z.string().datetime(),
ageDays: z.number().int().nonnegative(),
freshness: z.enum(["fresh", "aging", "stale"]),
providers: CatalogDeltaSchema,
models: CatalogDeltaSchema,
trend: z.enum(["growing", "stable", "shrinking"]),
})
.strict();
export const RadarIntelFeedSchema = z
.object({
feed: z.literal("omniroute-radar-intel"),
schemaVersion: z.literal(1),
version: z.string().regex(/^\d{4}\.\d{2}\.\d{2}\.\d+$/),
generatedAt: z.string().datetime(),
tier: z.literal("live"),
methodology: z
.object({
kind: z.literal("elo"),
initialRating: z.literal(1000),
kFactor: z.literal(32),
})
.strict(),
rankings: z.array(RadarIntelRankingSchema),
catalog: RadarIntelCatalogSchema,
})
.strict();
export type RadarIntelFeed = z.infer<typeof RadarIntelFeedSchema>;
export type RadarIntelRanking = z.infer<typeof RadarIntelRankingSchema>;
export type RadarIntelCatalog = z.infer<typeof RadarIntelCatalogSchema>;

173
src/lib/radar/intelSync.ts Normal file
View File

@@ -0,0 +1,173 @@
/** Server-side sync for the signed, supporter-only Radar Intel feed. */
import crypto from "node:crypto";
import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error";
import { isFeatureFlagEnabled } from "@/shared/utils/featureFlags";
import { RadarIntelFeedSchema, type RadarIntelFeed } from "./intelFeedSchema";
import { compareVersions, type RadarSettingsSnapshot } from "./sync";
import { verifyFeedBytes } from "./verify";
const DEFAULT_FEED_BASE_URL = "https://radar.omniroute.online";
const SYNC_TIMEOUT_MS = 30_000;
const MAX_FEED_BYTES = 10 * 1024 * 1024;
export type IntelSyncStatus =
| { status: "disabled" }
| { status: "opt_out" }
| { status: "no_key" }
| { status: "invalid_signature" }
| { status: "invalid_schema" }
| { status: "wrong_tier" }
| { status: "stale" }
| { status: "too_large" }
| { status: "updated"; version: string }
| { status: "error"; reason: string };
export interface RadarIntelCacheEntry {
version: string;
tier: "live";
payload: string;
signature: string;
supporterIdentity: string;
fetchedAt?: string;
}
export interface IntelSyncDeps {
fetch?: typeof globalThis.fetch;
now?: () => Date;
getFlag?: (key: string) => boolean;
getSettings?: () => RadarSettingsSnapshot;
getCache?: () => RadarIntelCacheEntry | null;
setCache?: (entry: RadarIntelCacheEntry) => void;
recognizeSupporter?: (identity: string) => Promise<void>;
}
async function readBoundedBytes(response: Response): Promise<Buffer | null> {
const contentLength = response.headers.get("content-length");
if (contentLength !== null) {
const declared = Number(contentLength);
if (Number.isFinite(declared) && declared > MAX_FEED_BYTES) return null;
}
const body = response.body as ReadableStream<Uint8Array> | null | undefined;
if (!body || typeof body.getReader !== "function") {
const buffered = Buffer.from(await response.arrayBuffer());
return buffered.byteLength > MAX_FEED_BYTES ? null : buffered;
}
const reader = body.getReader();
const chunks: Uint8Array[] = [];
let total = 0;
for (;;) {
const { done, value } = await reader.read();
if (done) break;
if (!value) continue;
total += value.byteLength;
if (total > MAX_FEED_BYTES) {
await reader.cancel().catch(() => undefined);
return null;
}
chunks.push(value);
}
return Buffer.concat(chunks.map((chunk) => Buffer.from(chunk)));
}
function supporterIdentity(key: string): string {
return `radar:${crypto.createHash("sha256").update(key, "utf8").digest("hex")}`;
}
async function recognizeVerifiedSupporter(identity: string): Promise<void> {
const { emitGamificationEvent } = await import("@/lib/gamification/events");
await emitGamificationEvent({ apiKeyId: identity, action: "radar_supporter" });
}
export async function syncRadarIntel(deps: IntelSyncDeps = {}): Promise<IntelSyncStatus> {
const {
fetch: fetchFn = globalThis.fetch,
now = () => new Date(),
getFlag = isFeatureFlagEnabled,
getSettings: getSettingsFn,
getCache: getCacheFn,
setCache: setCacheFn,
recognizeSupporter = recognizeVerifiedSupporter,
} = deps;
try {
if (!getFlag("RADAR_ENABLED")) return { status: "disabled" };
const settings = getSettingsFn
? getSettingsFn()
: (await import("@/lib/db/radar")).getRadarSettings();
if (!settings.optIn) return { status: "opt_out" };
if (!settings.supporterKey) return { status: "no_key" };
const baseUrl = (process.env.RADAR_FEED_URL || DEFAULT_FEED_BASE_URL).replace(/\/+$/, "");
const response = await fetchFn(`${baseUrl}/v1/intel/latest`, {
method: "GET",
headers: { Authorization: `Bearer ${settings.supporterKey}` },
signal: AbortSignal.timeout(SYNC_TIMEOUT_MS),
});
if (!response.ok) {
return {
status: "error",
reason: `Intel feed request failed with status ${response.status}`,
};
}
const rawBytes = await readBoundedBytes(response);
if (!rawBytes) return { status: "too_large" };
const signature = response.headers.get("x-omniroute-feed-signature") ?? "";
if (!verifyFeedBytes(rawBytes, signature)) return { status: "invalid_signature" };
let feed: RadarIntelFeed;
try {
feed = RadarIntelFeedSchema.parse(JSON.parse(rawBytes.toString("utf8")));
} catch {
return { status: "invalid_schema" };
}
if (response.headers.get("x-omniroute-feed-tier") !== "live" || feed.tier !== "live") {
return { status: "wrong_tier" };
}
const existing = getCacheFn
? getCacheFn()
: (await import("@/lib/db/radar")).getRadarIntelCache();
if (existing && compareVersions(feed.version, existing.version) <= 0) {
return { status: "stale" };
}
const identity = supporterIdentity(settings.supporterKey);
const cacheEntry: RadarIntelCacheEntry = {
version: feed.version,
tier: "live",
payload: rawBytes.toString("utf8"),
signature,
supporterIdentity: identity,
fetchedAt: now().toISOString(),
};
if (setCacheFn) setCacheFn(cacheEntry);
else (await import("@/lib/db/radar")).setRadarIntelCache(cacheEntry);
// Recognition is local and best-effort. It runs only after the signed live
// bytes have been accepted and persisted, and never changes sync success.
await recognizeSupporter(identity).catch(() => undefined);
return { status: "updated", version: feed.version };
} catch (error: unknown) {
const reason = (sanitizeErrorMessage(error) || "Radar Intel sync failed").replace(
/omr_[a-f0-9]{40}/gi,
"[REDACTED]"
);
return { status: "error", reason };
}
}
export const RADAR_INTEL_STALE_MS = 24 * 60 * 60 * 1000;
export function shouldSyncRadarIntel(fetchedAt: string | null, nowMs = Date.now()): boolean {
if (!fetchedAt) return true;
const fetchedMs = Date.parse(fetchedAt);
return !Number.isFinite(fetchedMs) || nowMs - fetchedMs >= RADAR_INTEL_STALE_MS;
}

View File

@@ -24,7 +24,15 @@
*/
import { isFeatureFlagEnabled } from "@/shared/utils/featureFlags";
import { getRadarCache, getRadarSettings, getRadarReferralsCache } from "@/lib/db/radar";
import {
getRadarCache,
getRadarIntelCache,
getRadarOffersCache,
getRadarSettings,
getRadarReferralsCache,
} from "@/lib/db/radar";
import { shouldSyncRadarIntel, syncRadarIntel, type IntelSyncStatus } from "./intelSync";
import { syncRadarOffers, type OffersSyncStatus } from "./offersSync";
import { nextSyncTime, syncRadar, type SyncStatus } from "./sync";
import {
syncRadarReferrals,
@@ -49,6 +57,10 @@ export interface RadarSchedulerDeps {
getReferralsCache?: () => { fetchedAt: string } | null;
/** Referrals sync — separate from `sync` (the catalog sync). */
syncReferrals?: () => Promise<ReferralsSyncStatus>;
getOffersCache?: () => { fetchedAt: string } | null;
syncOffers?: () => Promise<OffersSyncStatus>;
getIntelCache?: () => { fetchedAt: string } | null;
syncIntel?: () => Promise<IntelSyncStatus>;
now?: () => number;
setIntervalFn?: typeof setInterval;
clearIntervalFn?: typeof clearInterval;
@@ -73,6 +85,28 @@ async function maybeSyncReferrals(deps: RadarSchedulerDeps, nowMs: number): Prom
}
}
async function maybeSyncSupporterFeeds(deps: RadarSchedulerDeps, nowMs: number): Promise<void> {
try {
const offersCache = (deps.getOffersCache ?? getRadarOffersCache)();
if (nowMs >= nextSyncTime(offersCache?.fetchedAt ?? null).getTime()) {
await (deps.syncOffers ?? syncRadarOffers)();
}
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
console.warn("[RADAR_SYNC] Offers side-sync failed (non-fatal):", msg);
}
try {
const intelCache = (deps.getIntelCache ?? getRadarIntelCache)();
if (shouldSyncRadarIntel(intelCache?.fetchedAt ?? null, nowMs)) {
await (deps.syncIntel ?? syncRadarIntel)();
}
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
console.warn("[RADAR_SYNC] Intel side-sync failed (non-fatal):", msg);
}
}
/**
* One scheduler evaluation. Exported for tests and for the immediate
* post-start tick.
@@ -92,6 +126,7 @@ export async function radarSchedulerTick(deps: RadarSchedulerDeps = {}): Promise
// Referrals sync on their own staleness window — independent of the
// catalog's due-ness below, same tick.
await maybeSyncReferrals(deps, nowMs);
await maybeSyncSupporterFeeds(deps, nowMs);
const cache = (deps.getCache ?? getRadarCache)();
if (nowMs < nextSyncTime(cache?.fetchedAt ?? null).getTime()) {

View File

@@ -0,0 +1,42 @@
{
"feed": "omniroute-radar-intel",
"schemaVersion": 1,
"version": "2026.08.09.1",
"generatedAt": "2026-08-09T12:00:00.000Z",
"tier": "live",
"methodology": { "kind": "elo", "initialRating": 1000, "kFactor": 32 },
"rankings": [
{
"rank": 1,
"provider": "example-a",
"modelId": "example-model-a",
"category": "general",
"rating": 1016,
"matches": 1,
"wins": 1,
"losses": 0,
"draws": 0
},
{
"rank": 2,
"provider": "example-b",
"modelId": "example-model-b",
"category": "general",
"rating": 984,
"matches": 1,
"wins": 0,
"losses": 1,
"draws": 0
}
],
"catalog": {
"currentVersion": "2026.08.09.1",
"previousVersion": "2026.08.08.1",
"currentGeneratedAt": "2026-08-09T11:00:00.000Z",
"ageDays": 0,
"freshness": "fresh",
"providers": { "current": 2, "added": 1, "removed": 0 },
"models": { "current": 2, "added": 1, "removed": 0 },
"trend": "growing"
}
}

View File

@@ -0,0 +1,74 @@
import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import test from "node:test";
const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-radar-intel-db-"));
process.env.DATA_DIR = TEST_DATA_DIR;
process.env.STORAGE_ENCRYPTION_KEY = "test-encryption-key-for-radar-intel-db-32b!";
const core = await import("../../src/lib/db/core.ts");
const radar = await import("../../src/lib/db/radar.ts");
function resetStorage(): void {
core.resetDbInstance();
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
fs.mkdirSync(TEST_DATA_DIR, { recursive: true });
}
test.beforeEach(resetStorage);
test.after(() => {
core.resetDbInstance();
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
});
test("Intel migration provides a byte-preserving single-row cache", () => {
assert.equal(radar.getRadarIntelCache(), null);
radar.setRadarIntelCache({
version: "2026.08.09.1",
tier: "live",
payload: '{"exact":true}\n',
signature: "signed",
supporterIdentity: `radar:${"a".repeat(64)}`,
fetchedAt: "2026-08-09T12:05:00.000Z",
});
assert.deepEqual(radar.getRadarIntelCache(), {
version: "2026.08.09.1",
tier: "live",
payload: '{"exact":true}\n',
signature: "signed",
supporterIdentity: `radar:${"a".repeat(64)}`,
fetchedAt: "2026-08-09T12:05:00.000Z",
});
});
test("changing supporter key invalidates catalog, referrals, offers, and Intel atomically", () => {
radar.setRadarCache({ version: "2026.08.09.1", tier: "live", payload: "{}", signature: "a" });
radar.setRadarReferralsCache({
generatedAt: "2026-08-09T12:00:00.000Z",
tier: "live",
payload: "{}",
signature: "b",
});
radar.setRadarOffersCache({
version: "2026.08.09.1",
tier: "live",
payload: "{}",
signature: "c",
});
radar.setRadarIntelCache({
version: "2026.08.09.1",
tier: "live",
payload: "{}",
signature: "d",
supporterIdentity: `radar:${"a".repeat(64)}`,
});
radar.setRadarKey(`omr_${"b".repeat(40)}`);
assert.equal(radar.getRadarCache(), null);
assert.equal(radar.getRadarReferralsCache(), null);
assert.equal(radar.getRadarOffersCache(), null);
assert.equal(radar.getRadarIntelCache(), null);
});

View File

@@ -0,0 +1,107 @@
import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import test from "node:test";
import { SignJWT } from "jose";
const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-radar-intel-routes-"));
process.env.DATA_DIR = TEST_DATA_DIR;
process.env.STORAGE_ENCRYPTION_KEY = "test-encryption-key-for-radar-intel-routes-32b!";
process.env.JWT_SECRET = "test-jwt-secret-for-radar-intel-routes";
process.env.INITIAL_PASSWORD = "test-bootstrap-password-for-radar-intel-routes";
const core = await import("../../src/lib/db/core.ts");
const radarDb = await import("../../src/lib/db/radar.ts");
async function authHeaders(): Promise<Record<string, string>> {
const token = await new SignJWT({ authenticated: true })
.setProtectedHeader({ alg: "HS256" })
.setIssuedAt()
.setExpirationTime("1h")
.sign(new TextEncoder().encode(process.env.JWT_SECRET));
return { Cookie: `auth_token=${token}` };
}
function request(pathname: string, method: "GET" | "POST", headers: Record<string, string> = {}) {
return new Request(`http://localhost:20128${pathname}`, { method, headers });
}
function resetStorage(): void {
core.resetDbInstance();
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
fs.mkdirSync(TEST_DATA_DIR, { recursive: true });
}
test.after(() => {
core.resetDbInstance();
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
delete process.env.RADAR_ENABLED;
});
test("Intel, status, and aggregate sync routes are 404 before auth when flag is off", async () => {
resetStorage();
delete process.env.RADAR_ENABLED;
const intel = await import("../../src/app/api/radar/intel/route.ts");
const intelSync = await import("../../src/app/api/radar/intel/sync/route.ts");
const status = await import("../../src/app/api/radar/status/route.ts");
const syncAll = await import("../../src/app/api/radar/sync-all/route.ts");
assert.equal((await intel.GET(request("/api/radar/intel", "GET"))).status, 404);
assert.equal((await intelSync.POST(request("/api/radar/intel/sync", "POST"))).status, 404);
assert.equal((await status.GET(request("/api/radar/status", "GET"))).status, 404);
assert.equal((await syncAll.POST(request("/api/radar/sync-all", "POST"))).status, 404);
});
test("verified local Intel is returned without supporter identity or key material", async () => {
resetStorage();
process.env.RADAR_ENABLED = "true";
const payload = fs.readFileSync(
path.resolve(process.cwd(), "tests/fixtures/radar-intel-canonical.json"),
"utf8"
);
radarDb.setRadarIntelCache({
version: "2026.08.09.1",
tier: "live",
payload,
signature: "fixture-signature",
supporterIdentity: `radar:${"a".repeat(64)}`,
fetchedAt: "2026-08-09T12:05:00.000Z",
});
const { GET } = await import("../../src/app/api/radar/intel/route.ts");
const response = await GET(request("/api/radar/intel", "GET", await authHeaders()));
const body = await response.json();
assert.equal(response.status, 200);
assert.equal(body.intel.rankings.length, 2);
assert.equal(body.meta.supporterVerified, true);
assert.ok(!JSON.stringify(body).includes("radar:"));
assert.ok(!JSON.stringify(body).includes("omr_"));
});
test("Radar status is read-only and aggregate sync reports each feed separately", async () => {
resetStorage();
process.env.RADAR_ENABLED = "true";
const headers = await authHeaders();
const statusRoute = await import("../../src/app/api/radar/status/route.ts");
const status = await statusRoute.GET(request("/api/radar/status", "GET", headers));
const statusBody = await status.json();
assert.deepEqual(statusBody.settings, { optIn: false, hasSupporterKey: false });
assert.deepEqual(Object.keys(statusBody.feeds).sort(), [
"catalog",
"intel",
"offers",
"referrals",
]);
const syncAllRoute = await import("../../src/app/api/radar/sync-all/route.ts");
const synced = await syncAllRoute.POST(request("/api/radar/sync-all", "POST", headers));
const syncBody = await synced.json();
assert.deepEqual(syncBody, {
catalog: { status: "opt_out" },
referrals: { status: "opt_out" },
offers: { status: "opt_out" },
intel: { status: "opt_out" },
});
});

View File

@@ -0,0 +1,210 @@
import assert from "node:assert/strict";
import crypto from "node:crypto";
import { readFile } from "node:fs/promises";
import test from "node:test";
const { publicKey, privateKey } = crypto.generateKeyPairSync("ed25519");
process.env.RADAR_FEED_PUBKEY = publicKey
.export({ type: "spki", format: "der" })
.toString("base64");
const intelSync = await import("../../src/lib/radar/intelSync.ts");
const { RadarIntelFeedSchema } = await import("../../src/lib/radar/intelFeedSchema.ts");
async function fixtureBytes(): Promise<Buffer> {
return readFile(new URL("../fixtures/radar-intel-canonical.json", import.meta.url));
}
function sign(bytes: Buffer): string {
return crypto.sign(null, bytes, privateKey).toString("base64");
}
function response(body: Buffer, headers: Record<string, string> = {}, status = 200): Response {
return {
ok: status >= 200 && status < 300,
status,
headers: new Headers(headers),
arrayBuffer: async () => body.buffer.slice(body.byteOffset, body.byteOffset + body.byteLength),
} as Response;
}
const supporterKey = `omr_${"a".repeat(40)}`;
const liveSettings = { optIn: true, supporterKey };
test("canonical Intel fixture is byte-identical to the private contract", async () => {
const bytes = await fixtureBytes();
assert.equal(bytes.byteLength, 1024);
assert.equal(
crypto.createHash("sha256").update(bytes).digest("hex"),
"c36aaa6ad53942afa0325d6b0fad0aa048ef66f24c805b743b9815446b0e6176"
);
assert.equal(RadarIntelFeedSchema.parse(JSON.parse(bytes.toString("utf8"))).tier, "live");
});
test("Intel schema rejects telemetry and inconsistent ranking counters", async () => {
const feed = JSON.parse((await fixtureBytes()).toString("utf8"));
assert.equal(RadarIntelFeedSchema.safeParse({ ...feed, uptime: 99.9 }).success, false);
feed.rankings[0].matches = 2;
assert.equal(RadarIntelFeedSchema.safeParse(feed).success, false);
});
test("Intel sync gates before fetch and only accepts exact signed live bytes", async () => {
for (const expected of ["disabled", "opt_out", "no_key"] as const) {
let fetched = false;
const result = await intelSync.syncRadarIntel({
getFlag: () => expected !== "disabled",
getSettings: () =>
expected === "opt_out"
? { optIn: false, supporterKey: null }
: { optIn: true, supporterKey: null },
fetch: (async () => {
fetched = true;
return response(Buffer.from("{}"));
}) as typeof fetch,
});
assert.equal(result.status, expected);
assert.equal(fetched, false);
}
const bytes = await fixtureBytes();
const writes: intelSync.RadarIntelCacheEntry[] = [];
const supporterIdentities: string[] = [];
let authorization = "";
const result = await intelSync.syncRadarIntel({
getFlag: () => true,
getSettings: () => liveSettings,
getCache: () => null,
setCache: (entry) => writes.push(entry),
recognizeSupporter: async (identity) => supporterIdentities.push(identity),
fetch: (async (_input, init) => {
authorization = new Headers(init?.headers).get("authorization") ?? "";
return response(bytes, {
"x-omniroute-feed-signature": sign(bytes),
"x-omniroute-feed-tier": "live",
});
}) as typeof fetch,
now: () => new Date("2026-08-09T12:05:00.000Z"),
});
assert.deepEqual(result, { status: "updated", version: "2026.08.09.1" });
assert.equal(authorization, `Bearer ${supporterKey}`);
assert.equal(writes[0]?.payload, bytes.toString("utf8"));
assert.equal(writes[0]?.tier, "live");
assert.match(writes[0]?.supporterIdentity ?? "", /^radar:[a-f0-9]{64}$/);
assert.deepEqual(supporterIdentities, [writes[0]?.supporterIdentity]);
assert.ok(!writes[0]?.supporterIdentity.includes(supporterKey));
});
test("Intel sync preserves the good cache on signature, tier, schema, replay, and size failures", async () => {
const bytes = await fixtureBytes();
const validSignature = sign(bytes);
const cases = [
{ expected: "invalid_signature", body: bytes, signature: "bad", tier: "live" },
{ expected: "wrong_tier", body: bytes, signature: validSignature, tier: "community" },
{
expected: "invalid_schema",
body: Buffer.from('{"feed":"wrong"}'),
signature: "",
tier: "live",
},
];
for (const item of cases) {
const signature = item.expected === "invalid_schema" ? sign(item.body) : item.signature;
let written = false;
const result = await intelSync.syncRadarIntel({
getFlag: () => true,
getSettings: () => liveSettings,
getCache: () => ({
version: "2026.08.08.1",
tier: "live",
payload: "last-good",
signature: "old",
supporterIdentity: `radar:${"b".repeat(64)}`,
}),
setCache: () => {
written = true;
},
fetch: (async () =>
response(item.body, {
"x-omniroute-feed-signature": signature,
"x-omniroute-feed-tier": item.tier,
})) as typeof fetch,
});
assert.equal(result.status, item.expected);
assert.equal(written, false);
}
let written = false;
const stale = await intelSync.syncRadarIntel({
getFlag: () => true,
getSettings: () => liveSettings,
getCache: () => ({
version: "2026.08.09.1",
tier: "live",
payload: "last-good",
signature: "old",
supporterIdentity: `radar:${"b".repeat(64)}`,
}),
setCache: () => {
written = true;
},
fetch: (async () =>
response(bytes, {
"x-omniroute-feed-signature": validSignature,
"x-omniroute-feed-tier": "live",
})) as typeof fetch,
});
assert.equal(stale.status, "stale");
const oversized = await intelSync.syncRadarIntel({
getFlag: () => true,
getSettings: () => liveSettings,
getCache: () => null,
setCache: () => {
written = true;
},
fetch: (async () =>
response(Buffer.from("ignored"), {
"content-length": String(10 * 1024 * 1024 + 1),
})) as typeof fetch,
});
assert.equal(oversized.status, "too_large");
assert.equal(written, false);
});
test("Intel sync enforces the byte cap while reading streamed chunks", async () => {
let cancelled = false;
let written = false;
const firstChunk = new Uint8Array(6 * 1024 * 1024);
const secondChunk = new Uint8Array(5 * 1024 * 1024);
const chunks = [firstChunk, secondChunk];
let chunkIndex = 0;
const body = new ReadableStream<Uint8Array>({
pull(controller) {
controller.enqueue(chunks[chunkIndex]);
chunkIndex += 1;
},
cancel() {
cancelled = true;
},
});
const result = await intelSync.syncRadarIntel({
getFlag: () => true,
getSettings: () => liveSettings,
getCache: () => null,
setCache: () => {
written = true;
},
fetch: (async () =>
new Response(body, {
status: 200,
headers: { "x-omniroute-feed-tier": "live" },
})) as typeof fetch,
});
assert.equal(result.status, "too_large");
assert.equal(cancelled, true);
assert.equal(written, false);
});

View File

@@ -46,10 +46,14 @@ function fakeTimers() {
function deps(overrides: Record<string, unknown> = {}) {
const syncCalls: number[] = [];
const referralsSyncCalls: number[] = [];
const offersSyncCalls: number[] = [];
const intelSyncCalls: number[] = [];
const timers = fakeTimers();
return {
syncCalls,
referralsSyncCalls,
offersSyncCalls,
intelSyncCalls,
timers,
d: {
getFlag: () => true,
@@ -66,7 +70,21 @@ function deps(overrides: Record<string, unknown> = {}) {
getReferralsCache: () => ({ fetchedAt: REFERRALS_FRESH }),
syncReferrals: async () => {
referralsSyncCalls.push(1);
return { status: "updated", generatedAt: "2026-08-06T12:00:00.000Z", tier: "live" } as const;
return {
status: "updated",
generatedAt: "2026-08-06T12:00:00.000Z",
tier: "live",
} as const;
},
getOffersCache: () => ({ fetchedAt: FRESH }),
syncOffers: async () => {
offersSyncCalls.push(1);
return { status: "updated", version: "2026.08.06.1" } as const;
},
getIntelCache: () => ({ fetchedAt: FRESH }),
syncIntel: async () => {
intelSyncCalls.push(1);
return { status: "updated", version: "2026.08.06.1" } as const;
},
now: () => NOW,
setIntervalFn: timers.setIntervalFn,
@@ -126,18 +144,21 @@ test("radar sync scheduler", async (t) => {
assert.equal(syncCalls.length, 1);
});
await t.test("ensure: registers one hourly timer, fires an immediate tick, idempotent", async () => {
const { d, timers, syncCalls } = deps();
assert.equal(ensureRadarSyncScheduler(d), true);
assert.equal(timers.registered.length, 1);
assert.equal(timers.registered[0].ms, RADAR_SCHEDULER_TICK_MS);
// The immediate tick is fire-and-forget; give the microtask queue a turn.
await new Promise((resolve) => setImmediate(resolve));
assert.equal(syncCalls.length, 1, "immediate tick should have synced the stale cache");
// Second ensure is a no-op — no second timer.
assert.equal(ensureRadarSyncScheduler(d), false);
assert.equal(timers.registered.length, 1);
});
await t.test(
"ensure: registers one hourly timer, fires an immediate tick, idempotent",
async () => {
const { d, timers, syncCalls } = deps();
assert.equal(ensureRadarSyncScheduler(d), true);
assert.equal(timers.registered.length, 1);
assert.equal(timers.registered[0].ms, RADAR_SCHEDULER_TICK_MS);
// The immediate tick is fire-and-forget; give the microtask queue a turn.
await new Promise((resolve) => setImmediate(resolve));
assert.equal(syncCalls.length, 1, "immediate tick should have synced the stale cache");
// Second ensure is a no-op — no second timer.
assert.equal(ensureRadarSyncScheduler(d), false);
assert.equal(timers.registered.length, 1);
}
);
await t.test("init: flag off => never arms (flag-off boot stays timer-free)", () => {
const { d, timers } = deps({ getFlag: () => false });
@@ -174,61 +195,100 @@ test("radar sync scheduler", async (t) => {
// only) so the catalog-sync result shape/assertions above stay unchanged.
// -------------------------------------------------------------------------
await t.test("tick: referrals cache fresh => referrals sync NOT called (catalog path unaffected)", async () => {
const { d, syncCalls, referralsSyncCalls } = deps();
const result = await radarSchedulerTick(d);
assert.equal(result.action, "synced", "catalog was due and must still sync as before");
assert.equal(syncCalls.length, 1);
assert.equal(referralsSyncCalls.length, 0, "referrals cache was fresh — must not sync");
});
await t.test(
"tick: referrals cache fresh => referrals sync NOT called (catalog path unaffected)",
async () => {
const { d, syncCalls, referralsSyncCalls } = deps();
const result = await radarSchedulerTick(d);
assert.equal(result.action, "synced", "catalog was due and must still sync as before");
assert.equal(syncCalls.length, 1);
assert.equal(referralsSyncCalls.length, 0, "referrals cache was fresh — must not sync");
}
);
await t.test("tick: referrals cache stale => referrals sync called, independent of catalog due-ness", async () => {
const { d, syncCalls, referralsSyncCalls } = deps({
getCache: () => ({ fetchedAt: FRESH }), // catalog NOT due
getReferralsCache: () => ({ fetchedAt: REFERRALS_STALE }), // referrals due
});
const result = await radarSchedulerTick(d);
assert.deepEqual(result, { action: "skipped", reason: "not_due" }, "catalog result shape must stay unchanged");
assert.equal(syncCalls.length, 0, "catalog must not sync — it was not due");
assert.equal(referralsSyncCalls.length, 1, "referrals were due and must sync independently");
});
await t.test(
"tick: referrals cache stale => referrals sync called, independent of catalog due-ness",
async () => {
const { d, syncCalls, referralsSyncCalls } = deps({
getCache: () => ({ fetchedAt: FRESH }), // catalog NOT due
getReferralsCache: () => ({ fetchedAt: REFERRALS_STALE }), // referrals due
});
const result = await radarSchedulerTick(d);
assert.deepEqual(
result,
{ action: "skipped", reason: "not_due" },
"catalog result shape must stay unchanged"
);
assert.equal(syncCalls.length, 0, "catalog must not sync — it was not due");
assert.equal(referralsSyncCalls.length, 1, "referrals were due and must sync independently");
}
);
await t.test("tick: referrals cache missing => referrals sync called (missing counts as stale)", async () => {
const { d, referralsSyncCalls } = deps({
await t.test(
"tick: referrals cache missing => referrals sync called (missing counts as stale)",
async () => {
const { d, referralsSyncCalls } = deps({
getCache: () => ({ fetchedAt: FRESH }),
getReferralsCache: () => null,
});
await radarSchedulerTick(d);
assert.equal(referralsSyncCalls.length, 1);
}
);
await t.test(
"tick: flag off => referrals sync NOT called (stopped before any sync check)",
async () => {
const { d, referralsSyncCalls } = deps({
getFlag: () => false,
getReferralsCache: () => null, // would be due if ever reached
});
await radarSchedulerTick(d);
assert.equal(referralsSyncCalls.length, 0);
}
);
await t.test(
"tick: opt-in off => referrals sync NOT called (skipped before any sync check)",
async () => {
const { d, referralsSyncCalls } = deps({
getSettings: () => ({ optIn: false }),
getReferralsCache: () => null, // would be due if ever reached
});
await radarSchedulerTick(d);
assert.equal(referralsSyncCalls.length, 0);
}
);
await t.test(
"tick: referrals sync throwing => swallowed, catalog tick still completes normally",
async () => {
const { d, syncCalls } = deps({
getReferralsCache: () => ({ fetchedAt: REFERRALS_STALE }),
syncReferrals: async () => {
throw new Error("referrals upstream exploded");
},
});
const result = await radarSchedulerTick(d);
assert.equal(
result.action,
"synced",
"a throwing referrals sync must never break the catalog tick"
);
assert.equal(syncCalls.length, 1);
}
);
await t.test("tick: offers and Intel use independent staleness gates", async () => {
const { d, syncCalls, offersSyncCalls, intelSyncCalls } = deps({
getCache: () => ({ fetchedAt: FRESH }),
getReferralsCache: () => null,
});
await radarSchedulerTick(d);
assert.equal(referralsSyncCalls.length, 1);
});
await t.test("tick: flag off => referrals sync NOT called (stopped before any sync check)", async () => {
const { d, referralsSyncCalls } = deps({
getFlag: () => false,
getReferralsCache: () => null, // would be due if ever reached
});
await radarSchedulerTick(d);
assert.equal(referralsSyncCalls.length, 0);
});
await t.test("tick: opt-in off => referrals sync NOT called (skipped before any sync check)", async () => {
const { d, referralsSyncCalls } = deps({
getSettings: () => ({ optIn: false }),
getReferralsCache: () => null, // would be due if ever reached
});
await radarSchedulerTick(d);
assert.equal(referralsSyncCalls.length, 0);
});
await t.test("tick: referrals sync throwing => swallowed, catalog tick still completes normally", async () => {
const { d, syncCalls } = deps({
getReferralsCache: () => ({ fetchedAt: REFERRALS_STALE }),
syncReferrals: async () => {
throw new Error("referrals upstream exploded");
},
getOffersCache: () => ({ fetchedAt: STALE }),
getIntelCache: () => null,
});
const result = await radarSchedulerTick(d);
assert.equal(result.action, "synced", "a throwing referrals sync must never break the catalog tick");
assert.equal(syncCalls.length, 1);
assert.deepEqual(result, { action: "skipped", reason: "not_due" });
assert.equal(syncCalls.length, 0);
assert.equal(offersSyncCalls.length, 1);
assert.equal(intelSyncCalls.length, 1);
});
});