Skip to content

fix(usage-analytics): bound dimension filter arrays and values - #8697

Merged
iscekic merged 2 commits into
mainfrom
drain/usage-analytics-filter-bounds
Oct 24, 2026
Merged

iscekic merged 2 commits into
mainfrom
drain/usage-analytics-filter-bounds

Conversation

@iscekic

@iscekic iscekic commented Oct 22, 2026 •

Copy link
Copy Markdown
Collaborator

Evidence

Evidence: demonstrated — on the real Next runtime at this head, requests whose filter lists and values sit at the new limits reach the query path, and one value past either limit answers 400 BAD_REQUEST from input validation before the resolver runs. A same-stack differential against the branch base confirms the difference comes from the input-validation layer.

Change

apps/web/src/routers/usage-analytics-schemas.ts gains two shared, exported limits next to MAX_SCOPE_ORGANIZATION_IDS:

  • MAX_FILTER_VALUES = 500 — element count of every dimension filter list: features, models, modes, userIds, providers, projects and each excluded* twin.
  • MAX_FILTER_VALUE_LENGTH = 256 — length of each value in those lists.

One local FilterValuesSchema applies both limits, so all twelve fields stay in sync. Because the limits live in the shared filters schema, they cover getSummary, getTimeseries, getBreakdown, getTable and the OpenAPI input schema; a request outside them is rejected by input validation and answers BAD_REQUEST. The dashboard filter picker offers at most 100 suggestions per dimension, so a real selection stays well inside both limits.

apps/web/src/routers/usage-analytics-router.ts re-exports the two constants next to the existing cap.

Verification

  • pnpm --filter web exec jest --runInBand src/routers/usage-analytics-router.test.ts — 56 passed. Covers, for each of the twelve filter fields, at-the-limit accepted / one-over rejected for both count and value length, plus a procedure-level BAD_REQUEST test that also asserts no Snowflake call happens.

  • pnpm --filter web exec jest --runInBand src/lib/openapi/trpc-openapi.test.ts — 9 passed.

  • pnpm --filter web typecheck — clean.

  • oxlint --config .oxlintrc.json and oxfmt --check on the changed files — clean.

  • Local dev stack (pnpm dev:start nextjs, Compose Postgres + Redis) with a fake-login user, POST /api/trpc/usageAnalytics.getSummary at this head:

    Input Answer
    30 filter values query path (500 INTERNAL_SERVER_ERROR "Usage data temporarily unavailable", no analytics database in the local stack)
    500 filter values query path
    500 excluded-project values query path
    501 filter values 400 BAD_REQUEST, too_big with maximum: 500
    501 excluded-model values 400 BAD_REQUEST, too_big with maximum: 500
    501 user-id values 400 BAD_REQUEST, too_big with maximum: 500
    one 256-character value query path
    one 257-character value 400 BAD_REQUEST, too_big with maximum: 256
  • Differential on the same running stack: the two schema files were swapped to the branch base, the same requests were repeated, then the change was restored and the requests repeated again. Only the input-validation answer changed; the query path behaved the same for inputs at the limits.

  • The same calls in the shape the dashboard uses (GET input, 30 values) also reach the query path, and /usage?filter.include.feature=… with 30 values answers 200.

No visual change, so no screenshots.

Proxy answers

  1. Question: Array cap: maximum element count per usage-analytics filter array across the twelve fields?
    Answer: 500 per array
    Confidence: 0.55
  2. Question: Value length: maximum characters per filter value?
    Answer: 256 chars
    Confidence: 0.75
  3. Question: Scope: include the extra recommendations outside the approved brief (bounded getTimeseries date window, hard total-binding maximum per statement)?
    Answer: No — brief only
    Confidence: 0.65

Cap every dimension filter list at 500 values and every value at 256
characters in the shared filters schema, next to the existing organization
scope cap, and export both limits. Add schema boundary tests for all twelve
filter fields plus a procedure-level test that an over-limit filter answers
BAD_REQUEST without reaching the Snowflake path.
@iscekic iscekic added drain Work item from the findings drain janitor:trust Janitor finding: trust boundary hardening labels Oct 22, 2026
@kilo-code-bot

kilo-code-bot Bot commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Executive Summary

The change correctly bounds all twelve dimension filter lists (500 values) and each value (256 chars) in the shared Zod filters schema, propagates the limits to every procedure and the OpenAPI/MCP catalog schema, and covers the boundaries with schema- and procedure-level tests; no defects found in the changed code.

Assumptions: [INFERENCE] Total per-statement binding count is still unbounded (up to ~1,000 org ids plus 12 × 500 filter values, i.e. ~7,000 bindings, each ≤ 256 chars). The PR description states a hard total-binding maximum was explicitly out of scope, so this is noted as residual hardening rather than a finding; no Snowflake binding-count limit was verified.

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 0
Files Reviewed (4 files)
  • apps/web/src/routers/usage-analytics-schemas.ts - no issues (limits applied to all 12 filter fields via one shared FilterValuesSchema; existing organizationIds, limit, groupBy caps unchanged)
  • apps/web/src/routers/usage-analytics-router.ts - no issues (re-export only, no behavior change)
  • apps/web/src/routers/usage-analytics-router.test.ts - no issues (at-limit/over-limit coverage for count and value length on every field, plus BAD_REQUEST-without-Snowflake test; assertions match Zod v4 too_big issue shapes and paths)
  • services/kilo-mcp/catalog.json - no issues (generated output consistent with the schema: maxItems: 500 per list, maxLength: 256 per item)

Verified the dashboard filter picker requests at most 100 suggestions per dimension (FilterGeneratorPopover.tsx:99), so real UI selections remain well inside the new limits and no legitimate request path is newly rejected.


Reviewed by deepseek-flash-latest · Input: 59.9K · Output: 8.9K · Cached: 553.5K

Review guidance: REVIEW.md from base branch main

@iscekic iscekic added the merge:bot Bot-owned review; merge when ready once readiness passes and a review approves. label Oct 22, 2026
@iscekic
iscekic enabled auto-merge October 22, 2026 22:28
@iscekic iscekic added the intent:ok Janitor change confirmed as a real bug fix, not intended behavior label Oct 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

drain Work item from the findings drain intent:ok Janitor change confirmed as a real bug fix, not intended behavior janitor:trust Janitor finding: trust boundary hardening merge:bot Bot-owned review; merge when ready once readiness passes and a review approves.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants