fix(radar): close the audit gaps — auth, merged feed fields, opt-in state, sidebar gate, size cap + daily scheduler - #9686
Merged
Conversation
…ride applyFeed()'s MergedEntry shape omitted contextWindow/capabilities/limits/ setup even though FeedModel always carries them, so the dashboard's setup link, Context column, and capability badges never rendered and the setup page's provider lookup always failed. Both merge paths (mergeOne and feedModelToMerged) now copy the four fields through, respecting rule 1 (local override wins) same as every other field. feedModelToMerged() also unconditionally forced enabled:false when the feed disabled a feed-only entry, even when the operator had locally overridden enabled:true — mergeOne() already applies overrides after the disable rule and got this right. feedModelToMerged() now only force-disables when there is no local `enabled` override, matching mergeOne()'s semantics.
syncRadar() buffered the entire feed response via
Buffer.from(await res.arrayBuffer()) with no size limit, so a
misconfigured or hostile RADAR_FEED_URL (or an upstream serving garbage)
could force an unbounded in-memory buffer. Enforcement is two-layered: a
Content-Length preflight skips reading an already-oversized body entirely,
and a running-total check while reading the stream enforces the cap even
when Content-Length is absent or understates the real size — concatenating
the accumulated chunks preserves the exact bytes the signature check needs.
Exceeding the cap returns a new { status: "too_large" } SyncStatus and
leaves the cache untouched, following the same non-destructive pattern as
every other sync failure (invalid_signature/invalid_schema/stale).
The "radar" sidebar item was registered unconditionally in sidebarVisibility/sections.ts, but Sidebar.tsx has no feature-flag awareness (it's a client component), so the link stayed visible and clickable with RADAR_ENABLED off, landing on a 404 dashboard page. Sidebar items gain an opt-in `featureFlagKey` field plus a pure isSidebarItemVisibleForFlags() filter (fails open when a flag isn't in the map, so a missing/not-yet-loaded key never hides an unrelated item). The resolved flag value piggy-backs on the /api/settings response the sidebar already fetches on mount (new `radarEnabled` field) rather than adding a dedicated round trip.
GET /api/radar/catalog, POST /api/radar/sync, and POST /api/radar/settings
had zero authentication — any client that could reach the local server
could read the merged catalog, trigger a sync, or flip the opt-in/set the
supporter key. All three (plus the new GET below) now call
isAuthenticated() from the shared apiAuth guard, same gate as the rest of
/api/settings/*. The RADAR_ENABLED flag-off 404 check keeps running FIRST
so flag-off inertia stays byte-identical (no auth prompt just to learn the
surface doesn't exist); auth runs after it, before any DB access.
Adds GET /api/radar/settings, returning { optIn, hasSupporterKey,
supporterKeyMasked } — the raw key never leaves the server on either verb.
The dashboard page's fetchSettings() now calls this endpoint instead of
inferring opt-in state from the catalog response (which always defaulted
to unknown/null), so an already-activated operator no longer sees the
activation screen on every reload. handleSync() also handles the new
too_large sync status introduced by the response-cap fix, reusing the
existing generic sync-failed copy (no new UI strings).
- RADAR_FEED_URL default was documented as radar.omniroute.dev in ENVIRONMENT.md; the actual default (src/lib/radar/sync.ts) and every other reference use radar.omniroute.online — fix the one stale spot. - Correct the FREE_MODEL_BUDGETS source path: it's declared in freeModelCatalog.data.ts, not freeModelCatalog.ts (which only re-exports it). - Document that the signed feed body's `tier` is always "live" (one signed artifact per version) and the actually-served tier comes from the `x-omniroute-feed-tier` response header, resolved with a Zod parse + fallback to the body field. - Document that all four /api/radar/* routes now require auth (isAuthenticated(), same gate as /api/settings/*), the new GET /api/radar/settings route, and the new too_large sync status from the 10MB response cap.
Spec asks for a 1x/day sync while opted in and fresh data on every page open. The scheduler only arms itself when RADAR_ENABLED AND the opt-in are already on (boot) or right after the user opts in (settings route) — a flag-off install never creates the timer, preserving the inertia contract. The page auto-syncs once per mount when the cached feed is older than 6h.
Owner
Author
CI triage — every red check is inherited from the base (#9679), none is from this branch
All radar-scope validation is green locally: 119 radar/sidebar tests across 8 files (route tests run against a deduplicated migrations dir to bypass the inherited 134 collision), focused ESLint clean, typecheck clean for touched files, docs checks clean for touched docs. Merging per operator instruction with the base-red inheritance documented (base drain in progress via #9634). |
This was referenced Aug 7, 2026
5 tasks done
diegosouzapw
pushed a commit
that referenced
this pull request
Aug 25, 2026
…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.
muhamadgalihsaputra
pushed a commit
to niyatna/NiyatnaRoute
that referenced
this pull request
Sep 27, 2026
…r gate, size cap) + daily sync scheduler (diegosouzapw#9686) * fix(radar): preserve extended feed fields and honor local enable override applyFeed()'s MergedEntry shape omitted contextWindow/capabilities/limits/ setup even though FeedModel always carries them, so the dashboard's setup link, Context column, and capability badges never rendered and the setup page's provider lookup always failed. Both merge paths (mergeOne and feedModelToMerged) now copy the four fields through, respecting rule 1 (local override wins) same as every other field. feedModelToMerged() also unconditionally forced enabled:false when the feed disabled a feed-only entry, even when the operator had locally overridden enabled:true — mergeOne() already applies overrides after the disable rule and got this right. feedModelToMerged() now only force-disables when there is no local `enabled` override, matching mergeOne()'s semantics. * fix(radar): cap feed sync response body at 10MB syncRadar() buffered the entire feed response via Buffer.from(await res.arrayBuffer()) with no size limit, so a misconfigured or hostile RADAR_FEED_URL (or an upstream serving garbage) could force an unbounded in-memory buffer. Enforcement is two-layered: a Content-Length preflight skips reading an already-oversized body entirely, and a running-total check while reading the stream enforces the cap even when Content-Length is absent or understates the real size — concatenating the accumulated chunks preserves the exact bytes the signature check needs. Exceeding the cap returns a new { status: "too_large" } SyncStatus and leaves the cache untouched, following the same non-destructive pattern as every other sync failure (invalid_signature/invalid_schema/stale). * fix(radar): gate the sidebar radar item behind RADAR_ENABLED The "radar" sidebar item was registered unconditionally in sidebarVisibility/sections.ts, but Sidebar.tsx has no feature-flag awareness (it's a client component), so the link stayed visible and clickable with RADAR_ENABLED off, landing on a 404 dashboard page. Sidebar items gain an opt-in `featureFlagKey` field plus a pure isSidebarItemVisibleForFlags() filter (fails open when a flag isn't in the map, so a missing/not-yet-loaded key never hides an unrelated item). The resolved flag value piggy-backs on the /api/settings response the sidebar already fetches on mount (new `radarEnabled` field) rather than adding a dedicated round trip. * fix(radar): require auth on management routes, add GET settings GET /api/radar/catalog, POST /api/radar/sync, and POST /api/radar/settings had zero authentication — any client that could reach the local server could read the merged catalog, trigger a sync, or flip the opt-in/set the supporter key. All three (plus the new GET below) now call isAuthenticated() from the shared apiAuth guard, same gate as the rest of /api/settings/*. The RADAR_ENABLED flag-off 404 check keeps running FIRST so flag-off inertia stays byte-identical (no auth prompt just to learn the surface doesn't exist); auth runs after it, before any DB access. Adds GET /api/radar/settings, returning { optIn, hasSupporterKey, supporterKeyMasked } — the raw key never leaves the server on either verb. The dashboard page's fetchSettings() now calls this endpoint instead of inferring opt-in state from the catalog response (which always defaulted to unknown/null), so an already-activated operator no longer sees the activation screen on every reload. handleSync() also handles the new too_large sync status introduced by the response-cap fix, reusing the existing generic sync-failed copy (no new UI strings). * docs(radar): fix stale feed URL, document tier header/auth/size cap - RADAR_FEED_URL default was documented as radar.omniroute.dev in ENVIRONMENT.md; the actual default (src/lib/radar/sync.ts) and every other reference use radar.omniroute.online — fix the one stale spot. - Correct the FREE_MODEL_BUDGETS source path: it's declared in freeModelCatalog.data.ts, not freeModelCatalog.ts (which only re-exports it). - Document that the signed feed body's `tier` is always "live" (one signed artifact per version) and the actually-served tier comes from the `x-omniroute-feed-tier` response header, resolved with a Zod parse + fallback to the body field. - Document that all four /api/radar/* routes now require auth (isAuthenticated(), same gate as /api/settings/*), the new GET /api/radar/settings route, and the new too_large sync status from the 10MB response cap. * feat(radar): daily sync scheduler + auto-sync on page open Spec asks for a 1x/day sync while opted in and fresh data on every page open. The scheduler only arms itself when RADAR_ENABLED AND the opt-in are already on (boot) or right after the user opts in (settings route) — a flag-off install never creates the timer, preserving the inertia contract. The page auto-syncs once per mount when the cached feed is older than 6h. --------- Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
muhamadgalihsaputra
pushed a commit
to niyatna/NiyatnaRoute
that referenced
this pull request
Sep 27, 2026
…iegosouzapw#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 diegosouzapw#9686's treatment carefully, and the catalogUpdatedAt honesty (null over a fabricated download-time stand-in) is the right call.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes the gaps found in the 2026-08-06 Radar audit (PR #9515 follow-up), plus the spec'd daily sync.
Fixes
/api/radar/*routes (401 after the flag gate; flag-off 404 stays first, so flag-off inertia is byte-identical). NewGET /api/radar/settingsreturns opt-in state + masked key — the raw key never leaves the server.applyFeednow carriessetup/capabilities/contextWindow/limitsthrough both merge paths — the per-provider setup CTA, capability badges and context column render again.feedModelToMerged— a localenabled:trueoverride survives a feed disable, matchingmergeOne.RADAR_ENABLED(flag off ⇒ no menu entry; piggy-backs the existing/api/settingsfetch, fail-open for unrelated items).syncRadar(Content-Length preflight + streamed running total; exact bytes preserved for signature verification; newtoo_largestatus).ENVIRONMENT.mdstale feed URL fixed;RADAR.mddocuments the tier header (x-omniroute-feed-tieris the only tier truth — the signed body always sayslive), auth,GET settings, size cap, corrected data path.Validation
radar-api-routes19/19 (auth, 404/401, no stack leak, raw key never echoed),radar-apply-feed23/23,radar-sync48/48,sidebar-costs-section13/13,radar-scheduler+radar-auto-sync20/20,radar-page-state5/5,radar-flag-default6/6,radar-inertia5/5 — all green locally (route tests run against a deduplicated migrations dir because of the inherited 134 collision, see 🔴 Release branch not green: release/v3.8.50 #9679).typecheck:core: zero errors in touched files (pre-existing tip failures tracked in 🔴 Release branch not green: release/v3.8.50 #9679). Focused ESLint on all touched files: clean.check:docs-all: clean for touched docs.