Repository navigation
Conversation
…o 500 clampLimit rewrote every explicitly requested page size to the 500-row default, so bulk callers silently lost rows: metrics() asked for 10_000 traces to bucket the viewer's "memory writes per day" chart but only ever saw the newest 500 (~2 days at typical volume), sessions/embeddings undercounted, and world-model maintenance asking for 100_000 rows got 500. Explicit positive limits are now honored up to a MAX_QUERY_LIMIT ceiling of 100_000. The default page for callers that omit limit stays at 500, so no existing call site changes behavior. Addresses part of MemTensor#2401. Regression tests in tests/unit/storage/explicit-limit.test.ts and memory-core.test.ts fail on the old code and pass with this change.
🤖 Open Code ReviewTarget: PR #2535 🔍 OpenCodeReview found 4 issue(s) in this PR. 1.
|
Review follow-up: - Extract DEFAULT_PAGE_SIZE so the 500 default lives in one place (buildPageClauses and clampLimit both referenced a bare literal). - Bound caller-supplied limit params at the routes. Storage-level clampLimit now honors explicit limits up to MAX_QUERY_LIMIT for trusted in-process bulk reads, which relaxed the untrusted HTTP surface: GET /api/v1/traces (downstream listTraces clamps at 10_000), /api/v1/episodes (listEpisodes does not clamp at all), /api/v1/policies and /api/v1/world-models could each be asked for up to MAX_QUERY_LIMIT rows per request. New server/routes/params.ts parseLimitParam() restores the 500-row ceiling these endpoints had before this PR, and GET /api/v1/traces' documented "max 500" is now actually enforced at the route. - MAX_QUERY_LIMIT doc now states it is for trusted in-process bulk reads only; untrusted input is bounded by MAX_HTTP_PAGE_LIMIT.
|
Thanks for the review — both points addressed in 7a0f9b8. Point 2 (duplicated Point 1 (global 100k ceiling too coarse-grained) — accepted, with one amendment to the premise: I audited every untrusted entry before choosing where to fix it, and the actual exposure was not "any caller can fetch 100k rows" but a specific, smaller set:
Rather than threading per-caller caps through Side note from the audit: the policies over-fetch ( Verification — |
Description
clampLimit()inapps/memos-local-plugin/core/storage/repos/_helpers.tsrewrote every explicitly requested page size to the 500-row default page:Bulk callers therefore silently lost rows:
metrics()(memory-core.ts) readstraces.list({ limit: 10_000 })to bucket the viewer's "memory writes per day" chart — it only ever saw the newest 500 traces. On a real production DB (9,362 traces over 52 days, ~250/day) the chart showed data for exactly the last two days (253 + 247 = 500) and zero-filled the rest, while thesessionscard reported 14 instead of 163 andembeddingswas similarly undercounted.worldModel.list({ limit: 100_000 })) received 500 rows.Fix: explicit positive limits are now honored up to a
MAX_QUERY_LIMITceiling of 100,000 (the largest limit any call site asks for today). The default page for callers that omitlimitstays at 500 — no existing call site changes behavior, and unbounded scans remain impossible. No new dependencies.Relation to #2464 (cc @akushonkamen): same root cause, different contract decision. #2464 additionally changes omitted-
limitto mean "all rows" (noLIMITclause), which fixes more of #2401 but re-opens unbounded-scan risk on very large databases (the #2076 4.2 GB / 518k-row report). This PR is intentionally the minimal, zero-behavior-change subset: it can land independently (or serve as the safe core of that effort) while the omitted-limit semantics are decided separately.Related Issue (Required): Addresses the explicit-limit half of #2401 (no
Fixes— the omitted-limitcall sites it also reports are intentionally out of scope here).Type of change
How Has This Been Tested?
cd apps/memos-local-plugin && npm ci && npx vitest run tests/unittests/unit/storage/explicit-limit.test.ts:clampLimithonors explicit limits up toMAX_QUERY_LIMIT, invalid input keeps the 500 default, andrepo.list({ limit: 10_000 })over 600 seeded rows (3 distinct days) returns all 600 — including the oldest rows a day-bucketing caller needs — whilerepo.list()keeps the 500-row default page.tests/unit/pipeline/memory-core.test.ts("MemoryCore metrics sees beyond the 500-row page"): 520 traces "now" + 100 traces 3 days earlier;metrics({ days: 7 })must report the older day bucket as 100 andsessionsas 2. Fails on the old code with exactly the shipped symptom (expected +0 to be 100) and passes with this change.traces-count.test.tsthat enshrined the old clamp (list({ limit: 10000 })→ 500) to the new contract (→ 600).npm run lint(tsc --noEmit): clean.vitest run tests/uniton this branch: 9 pre-existing failing files, byte-identical to the same run on pristinemain(config/paths, install scripts, server http/sse — Windows-environment failures; see also the CI "ENV ISSUE" already reported on fix(plugin): stop clamping storage list queries to 500 rows #2464). This change adds zero new failures and 3 new passing tests.Checklist