mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-26 09:02:11 +03:00
fix(free-tier): serve the Radar overlay from /api/free-tier/summary (#11550)
Validated in a combined 4-PR batch worktree off release/v3.8.51 tip. Also removed an unused `crypto` import from the new test file (one-line lint fix, 228→229 unrelated-drift comparison confirmed it was the only new finding) — pushed to this branch. - Focused tests: free-tier-summary-radar-overlay.test.ts (9/9) + free-model-catalog/free-catalog-2026-07-expansion/free-providers-batch-2026-07 — part of batch's 60/60 node:test run - typecheck:core, file-size, changelog-integrity, complexity, cognitive-complexity, check:docs-counts-sync — all OK - Full-repo lint: 228 pre-existing dashboard react-hooks/* findings, unrelated to this diff (after the crypto-import fix) Thanks for this — the community/live feed entitlement distinction (never re-publishing paid feed content to anonymous callers) mirrors #9686's treatment carefully, and the catalogUpdatedAt honesty (null over a fabricated download-time stand-in) is the right call.
This commit is contained in:
@@ -0,0 +1 @@
|
||||
- The free-tier summary route now serves the refreshed Radar catalog when the feed is active — the same numbers the dashboard shows — and states which catalog answered plus its real build date, instead of always reporting release-frozen figures with a stale curation date. The supporter-key live feed stays reserved to authenticated callers of the instance.
|
||||
@@ -206,8 +206,23 @@ function dedupedSum(
|
||||
return loose;
|
||||
}
|
||||
|
||||
export function computeFreeModelTotals(opts: { excludeTosAvoid?: boolean } = {}): FreeModelTotals {
|
||||
const models = FREE_MODEL_BUDGETS.filter((m) => !(opts.excludeTosAvoid && m.tos === "avoid"));
|
||||
export function computeFreeModelTotals(
|
||||
opts: {
|
||||
excludeTosAvoid?: boolean;
|
||||
/**
|
||||
* The catalog to aggregate. Defaults to the static release baseline, so
|
||||
* every existing caller is unchanged. Callers that resolve a fresher
|
||||
* catalog (e.g. the Radar overlay) pass their entries here; an entry with
|
||||
* `enabled: false` contributes nothing, exactly as if it were absent.
|
||||
*/
|
||||
entries?: Array<FreeModelBudget & { enabled?: boolean }>;
|
||||
} = {}
|
||||
): FreeModelTotals {
|
||||
const catalog: ReadonlyArray<FreeModelBudget & { enabled?: boolean }> =
|
||||
opts.entries ?? FREE_MODEL_BUDGETS;
|
||||
const models = catalog.filter(
|
||||
(m) => !(opts.excludeTosAvoid && m.tos === "avoid") && m.enabled !== false
|
||||
);
|
||||
|
||||
const steadyRecurringTokens = dedupedSum(
|
||||
models,
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
import { computeFreeModelTotals } from "@omniroute/open-sse/config/freeModelCatalog.ts";
|
||||
import { FREE_CATALOG_CURATED_AT } from "@omniroute/open-sse/config/freeModelCatalog.data.ts";
|
||||
import { listNoCredentialProviders } from "@/shared/utils/providerCredentialRequirement";
|
||||
import {
|
||||
computeFreeModelTotals,
|
||||
type FreeModelBudget,
|
||||
} from "@omniroute/open-sse/config/freeModelCatalog.ts";
|
||||
import {
|
||||
FREE_CATALOG_CURATED_AT,
|
||||
FREE_MODEL_BUDGETS,
|
||||
} from "@omniroute/open-sse/config/freeModelCatalog.data.ts";
|
||||
import type { MergedEntry } from "@/lib/radar/applyFeed";
|
||||
import { getRadarCatalog } from "@/lib/radar";
|
||||
import { sumUsageTokensThisMonth } from "@/lib/db/usageSummary";
|
||||
import { isAuthenticated } from "@/shared/utils/apiAuth";
|
||||
import { listNoCredentialProviders } from "@/shared/utils/providerCredentialRequirement";
|
||||
|
||||
const CORS = {
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
@@ -9,6 +18,39 @@ const CORS = {
|
||||
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
||||
};
|
||||
|
||||
/**
|
||||
* `hardStopGuaranteed` is a hand-curated fact about the upstream provider that
|
||||
* the Radar feed does not carry, so it is read back from the baseline entry
|
||||
* rather than dropped when the overlay is served.
|
||||
*/
|
||||
const HARD_STOP_BY_KEY = new Map(
|
||||
FREE_MODEL_BUDGETS.filter((m) => m.hardStopGuaranteed !== undefined).map((m) => [
|
||||
`${m.provider}:${m.modelId}`,
|
||||
m.hardStopGuaranteed,
|
||||
])
|
||||
);
|
||||
|
||||
/**
|
||||
* Project a merged catalog entry back onto the response's `FreeModelBudget`
|
||||
* shape: the overlay's extra fields (origin, provenance, capabilities…) stay
|
||||
* internal, so the published contract is identical whichever source answered.
|
||||
*/
|
||||
function toBudgetEntry(entry: MergedEntry): FreeModelBudget & { enabled?: boolean } {
|
||||
return {
|
||||
provider: entry.provider,
|
||||
modelId: entry.modelId,
|
||||
displayName: entry.displayName,
|
||||
monthlyTokens: entry.monthlyTokens,
|
||||
creditTokens: entry.creditTokens,
|
||||
freeType: entry.freeType,
|
||||
poolKey: entry.poolKey,
|
||||
tos: entry.tos,
|
||||
trainsOnPrompts: entry.trainsOnPrompts,
|
||||
hardStopGuaranteed: HARD_STOP_BY_KEY.get(`${entry.provider}:${entry.modelId}`),
|
||||
enabled: entry.enabled,
|
||||
};
|
||||
}
|
||||
|
||||
export function OPTIONS(): Response {
|
||||
return new Response(null, { status: 204, headers: CORS });
|
||||
}
|
||||
@@ -16,13 +58,34 @@ export function OPTIONS(): Response {
|
||||
export async function GET(req: Request): Promise<Response> {
|
||||
const url = new URL(req.url);
|
||||
const excludeTosAvoid = url.searchParams.get("excludeTosAvoid") === "1";
|
||||
const totals = computeFreeModelTotals({ excludeTosAvoid });
|
||||
|
||||
// Same catalog resolution as the dashboard screens: one source of
|
||||
// truth, with meta === null meaning "the baseline answered" (flag off, no
|
||||
// cache, or corrupt cache).
|
||||
const { entries, meta } = getRadarCatalog();
|
||||
|
||||
// Entitlement follows the feed server's own download-time decision: the
|
||||
// community feed is the free public catalog, so it serves everyone; the
|
||||
// live feed is supporter-key content, so it only reaches callers this
|
||||
// instance has authenticated — never anonymous visitors, or an exposed
|
||||
// instance would re-publish the paid feed for free.
|
||||
const serveOverlay = meta !== null && (meta.tier !== "live" || (await isAuthenticated(req)));
|
||||
|
||||
const totals = serveOverlay
|
||||
? computeFreeModelTotals({ excludeTosAvoid, entries: entries.map(toBudgetEntry) })
|
||||
: computeFreeModelTotals({ excludeTosAvoid });
|
||||
|
||||
const usedThisMonth = sumUsageTokensThisMonth();
|
||||
const body = {
|
||||
...totals,
|
||||
usedThisMonth,
|
||||
remaining: Math.max(0, totals.steadyRecurringTokens - usedThisMonth),
|
||||
catalogUpdatedAt: FREE_CATALOG_CURATED_AT,
|
||||
// Which source answered, and the date of what was actually served — the
|
||||
// feed's own build date when the overlay answers (null when a cache row
|
||||
// predates build-date tracking; never the download time standing in),
|
||||
// the release curation date when the baseline does.
|
||||
catalogUpdatedAt: serveOverlay ? meta.generatedAt : FREE_CATALOG_CURATED_AT,
|
||||
catalogSource: serveOverlay ? ("radar-overlay" as const) : ("baseline" as const),
|
||||
// Computed here, not in the component: deriving it client-side would pull
|
||||
// the whole provider REGISTRY into the browser bundle.
|
||||
noCredentialProviders: listNoCredentialProviders(),
|
||||
|
||||
281
tests/unit/free-tier-summary-radar-overlay.test.ts
Normal file
281
tests/unit/free-tier-summary-radar-overlay.test.ts
Normal file
@@ -0,0 +1,281 @@
|
||||
/**
|
||||
* tests/unit/free-tier-summary-radar-overlay.test.ts
|
||||
*
|
||||
* GET /api/free-tier/summary used to answer from the release-frozen baseline
|
||||
* only, while the Radar overlay (flag RADAR_ENABLED) refreshed the very same
|
||||
* catalog for the dashboard screens — two doors, two answers to one question,
|
||||
* and the door that advertised a date was the stale one.
|
||||
*
|
||||
* The route now resolves its catalog through getRadarCatalog() like the
|
||||
* screens do, reports which source answered (`catalogSource`), and states the
|
||||
* date of what it ACTUALLY served: the feed's own build date when the overlay
|
||||
* answers (null when a pre-migration cache row carries none — never the
|
||||
* download time standing in for it), the curation date when the baseline does.
|
||||
*
|
||||
* Entitlement: the feed server decides community vs live at DOWNLOAD time
|
||||
* (supporter key). The local instance may re-serve what it legitimately holds,
|
||||
* but must not become a free relay of premium content when it is exposed to a
|
||||
* network:
|
||||
* - community tier => served to every caller (it is the free public feed);
|
||||
* - live tier => served to authenticated callers only; anonymous
|
||||
* callers keep the release-frozen baseline;
|
||||
* - flag off / no cache / corrupt cache => exactly today's behaviour
|
||||
* (the mirror of radar-inertia.test.ts).
|
||||
*/
|
||||
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { SignJWT } from "jose";
|
||||
|
||||
const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-free-tier-summary-"));
|
||||
process.env.DATA_DIR = TEST_DATA_DIR;
|
||||
process.env.STORAGE_ENCRYPTION_KEY = "test-encryption-key-for-free-tier-summary-tests-32b";
|
||||
process.env.JWT_SECRET = "test-jwt-secret-for-free-tier-summary-tests";
|
||||
process.env.INITIAL_PASSWORD = "test-bootstrap-password-for-free-tier-summary-tests";
|
||||
delete process.env.RADAR_ENABLED;
|
||||
|
||||
const core = await import("../../src/lib/db/core.ts");
|
||||
const { clearAllFeatureFlagOverrides, setFeatureFlagOverride } =
|
||||
await import("../../src/lib/db/featureFlags.ts");
|
||||
const radarDb = await import("../../src/lib/db/radar.ts");
|
||||
const { GET } = await import("../../src/app/api/free-tier/summary/route.ts");
|
||||
const { computeFreeModelTotals } = await import("../../open-sse/config/freeModelCatalog.ts");
|
||||
const { FREE_CATALOG_CURATED_AT } = await import("../../open-sse/config/freeModelCatalog.data.ts");
|
||||
|
||||
const GEN_AT = "2026-08-20T12:00:00.000Z";
|
||||
const FETCHED_AT = "2026-08-25T06:00:00.000Z";
|
||||
const OVERLAY_TOKENS = 1_234_567;
|
||||
const DISABLED_TOKENS = 9_999_999;
|
||||
|
||||
async function authCookieHeader(): Promise<string> {
|
||||
const secret = new TextEncoder().encode(process.env.JWT_SECRET);
|
||||
const token = await new SignJWT({ authenticated: true })
|
||||
.setProtectedHeader({ alg: "HS256" })
|
||||
.setIssuedAt()
|
||||
.setExpirationTime("1h")
|
||||
.sign(secret);
|
||||
return `auth_token=${token}`;
|
||||
}
|
||||
|
||||
function feedPayload(tier: "community" | "live") {
|
||||
return {
|
||||
feed: "omniroute-radar",
|
||||
schemaVersion: 1,
|
||||
version: "2026.08.25.1",
|
||||
generatedAt: GEN_AT,
|
||||
tier,
|
||||
counts: { providers: 1, models: 2 },
|
||||
providers: [{ id: "test-radar", name: "Test Radar" }],
|
||||
models: [
|
||||
{
|
||||
provider: "test-radar",
|
||||
modelId: "overlay-model",
|
||||
displayName: "Overlay Model",
|
||||
familyId: null,
|
||||
freeType: "recurring-monthly",
|
||||
budget: { kind: "per_model", tokensPerMonth: OVERLAY_TOKENS },
|
||||
limits: { rpm: null, rpd: null, tpm: null, tpd: null },
|
||||
contextWindow: null,
|
||||
capabilities: { tools: false, vision: false, thinking: false },
|
||||
trainsOnPrompts: null,
|
||||
tosRisk: "ok",
|
||||
setup: null,
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
provider: "test-radar",
|
||||
modelId: "retired-model",
|
||||
displayName: "Retired Model",
|
||||
familyId: null,
|
||||
freeType: "recurring-monthly",
|
||||
budget: { kind: "per_model", tokensPerMonth: DISABLED_TOKENS },
|
||||
limits: { rpm: null, rpd: null, tpm: null, tpd: null },
|
||||
contextWindow: null,
|
||||
capabilities: { tools: false, vision: false, thinking: false },
|
||||
trainsOnPrompts: null,
|
||||
tosRisk: "ok",
|
||||
setup: null,
|
||||
enabled: false,
|
||||
},
|
||||
],
|
||||
quirks: [],
|
||||
totals: {
|
||||
dedupedTokensPerMonth: OVERLAY_TOKENS + DISABLED_TOKENS,
|
||||
modelCount: 2,
|
||||
poolCount: 0,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function resetState() {
|
||||
core.resetDbInstance();
|
||||
try {
|
||||
if (fs.existsSync(TEST_DATA_DIR)) fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
fs.mkdirSync(TEST_DATA_DIR, { recursive: true });
|
||||
delete process.env.RADAR_ENABLED;
|
||||
clearAllFeatureFlagOverrides();
|
||||
}
|
||||
|
||||
function seedCache(tier: "community" | "live", opts: { generatedAt?: string | null } = {}) {
|
||||
radarDb.setRadarCache({
|
||||
version: "2026.08.25.1",
|
||||
generatedAt: "generatedAt" in opts ? (opts.generatedAt ?? undefined) : GEN_AT,
|
||||
tier,
|
||||
payload: JSON.stringify(feedPayload(tier)),
|
||||
signature: "test-signature-not-verified-on-read",
|
||||
fetchedAt: FETCHED_AT,
|
||||
});
|
||||
}
|
||||
|
||||
async function getBody(authenticated = false): Promise<Record<string, unknown>> {
|
||||
const res = await GET(
|
||||
new Request("https://omni.test/api/free-tier/summary", {
|
||||
headers: authenticated ? { Cookie: await authCookieHeader() } : {},
|
||||
})
|
||||
);
|
||||
assert.equal(res.status, 200);
|
||||
return (await res.json()) as Record<string, unknown>;
|
||||
}
|
||||
|
||||
// --- zero-delta default ------------------------------------------------------
|
||||
|
||||
test("flag off => the baseline answers, unchanged contract, honest source tag", async () => {
|
||||
resetState();
|
||||
const body = await getBody();
|
||||
|
||||
const expected = computeFreeModelTotals();
|
||||
assert.equal(body.catalogSource, "baseline");
|
||||
assert.equal(body.catalogUpdatedAt, FREE_CATALOG_CURATED_AT);
|
||||
assert.equal(body.steadyRecurringTokens, expected.steadyRecurringTokens);
|
||||
assert.equal(body.modelCount, expected.modelCount);
|
||||
});
|
||||
|
||||
test("excludeTosAvoid still filters on the baseline path", async () => {
|
||||
resetState();
|
||||
const res = await GET(new Request("https://omni.test/api/free-tier/summary?excludeTosAvoid=1"));
|
||||
assert.equal(res.status, 200);
|
||||
const body = (await res.json()) as Record<string, unknown>;
|
||||
|
||||
const expected = computeFreeModelTotals({ excludeTosAvoid: true });
|
||||
assert.equal(body.catalogSource, "baseline");
|
||||
assert.equal(body.steadyRecurringTokens, expected.steadyRecurringTokens);
|
||||
});
|
||||
|
||||
// --- community tier: the free public feed serves everyone --------------------
|
||||
|
||||
test("community tier, anonymous caller => the overlay answers, honestly dated", async () => {
|
||||
resetState();
|
||||
setFeatureFlagOverride("RADAR_ENABLED", "true");
|
||||
seedCache("community");
|
||||
|
||||
const body = await getBody(false);
|
||||
|
||||
assert.equal(body.catalogSource, "radar-overlay");
|
||||
// The honest date is the feed's BUILD date, not this install's download time.
|
||||
assert.equal(body.catalogUpdatedAt, GEN_AT);
|
||||
assert.notEqual(body.catalogUpdatedAt, FETCHED_AT);
|
||||
|
||||
const baselineSteady = computeFreeModelTotals().steadyRecurringTokens;
|
||||
assert.equal(
|
||||
body.steadyRecurringTokens,
|
||||
(baselineSteady as number) + OVERLAY_TOKENS,
|
||||
"the feed-only enabled model joins the steady headline"
|
||||
);
|
||||
});
|
||||
|
||||
test("community tier, authenticated caller => same overlay", async () => {
|
||||
resetState();
|
||||
setFeatureFlagOverride("RADAR_ENABLED", "true");
|
||||
seedCache("community");
|
||||
|
||||
const body = await getBody(true);
|
||||
|
||||
assert.equal(body.catalogSource, "radar-overlay");
|
||||
assert.equal(body.catalogUpdatedAt, GEN_AT);
|
||||
});
|
||||
|
||||
test("an entry the feed disables is excluded from the served totals", async () => {
|
||||
resetState();
|
||||
setFeatureFlagOverride("RADAR_ENABLED", "true");
|
||||
seedCache("community");
|
||||
|
||||
const body = await getBody(false);
|
||||
|
||||
const perModel = body.perModel as Array<{ modelId: string }>;
|
||||
assert.ok(!perModel.some((m) => m.modelId === "retired-model"));
|
||||
assert.equal(
|
||||
body.steadyRecurringTokens,
|
||||
(computeFreeModelTotals().steadyRecurringTokens as number) + OVERLAY_TOKENS
|
||||
);
|
||||
});
|
||||
|
||||
test("a pre-migration cache row serves the overlay with an UNKNOWN date, not the fetch time", async () => {
|
||||
resetState();
|
||||
setFeatureFlagOverride("RADAR_ENABLED", "true");
|
||||
seedCache("community", { generatedAt: undefined });
|
||||
|
||||
const body = await getBody(false);
|
||||
|
||||
assert.equal(body.catalogSource, "radar-overlay");
|
||||
assert.equal(
|
||||
body.catalogUpdatedAt,
|
||||
null,
|
||||
"unknown must stay unknown — fetchedAt would re-create the confusion generated_at exists to end"
|
||||
);
|
||||
});
|
||||
|
||||
// --- live tier: paid content stays behind this instance's sessions ----------
|
||||
|
||||
test("live tier, ANONYMOUS caller => the baseline answers, never the premium feed", async () => {
|
||||
resetState();
|
||||
setFeatureFlagOverride("RADAR_ENABLED", "true");
|
||||
seedCache("live");
|
||||
|
||||
const body = await getBody(false);
|
||||
|
||||
assert.equal(body.catalogSource, "baseline");
|
||||
assert.equal(body.catalogUpdatedAt, FREE_CATALOG_CURATED_AT);
|
||||
assert.equal(body.steadyRecurringTokens, computeFreeModelTotals().steadyRecurringTokens);
|
||||
});
|
||||
|
||||
test("live tier, authenticated caller => this install's earned overlay", async () => {
|
||||
resetState();
|
||||
setFeatureFlagOverride("RADAR_ENABLED", "true");
|
||||
seedCache("live");
|
||||
|
||||
const body = await getBody(true);
|
||||
|
||||
assert.equal(body.catalogSource, "radar-overlay");
|
||||
assert.equal(body.catalogUpdatedAt, GEN_AT);
|
||||
assert.equal(
|
||||
body.steadyRecurringTokens,
|
||||
(computeFreeModelTotals().steadyRecurringTokens as number) + OVERLAY_TOKENS
|
||||
);
|
||||
});
|
||||
|
||||
// --- resilience --------------------------------------------------------------
|
||||
|
||||
test("corrupt cache => baseline fallback with the baseline contract", async () => {
|
||||
resetState();
|
||||
setFeatureFlagOverride("RADAR_ENABLED", "true");
|
||||
radarDb.setRadarCache({
|
||||
version: "broken",
|
||||
generatedAt: GEN_AT,
|
||||
tier: "community",
|
||||
payload: "{definitely not json",
|
||||
signature: "test-signature-not-verified-on-read",
|
||||
fetchedAt: FETCHED_AT,
|
||||
});
|
||||
|
||||
const body = await getBody(false);
|
||||
|
||||
assert.equal(body.catalogSource, "baseline");
|
||||
assert.equal(body.catalogUpdatedAt, FREE_CATALOG_CURATED_AT);
|
||||
assert.equal(body.steadyRecurringTokens, computeFreeModelTotals().steadyRecurringTokens);
|
||||
});
|
||||
Reference in New Issue
Block a user