Repository navigation
Description
Filed, not fixed: the sites are in objectstack-ai/objectui, outside #10288's file
surface. Census taken at objectui c40f3b8ca21ddc19e05682f1719fcf16d4ce7fba
(objectstack pins 9a3daf8d37ad973a621e5edd276fe32467f90684; re-confirm at the pin
before acting).
The class
QueryParams (packages/types/src/data.ts) declares only $-prefixed keys, and
ObjectStackAdapter.convertQueryParams (packages/data-objectstack/src/index.ts)
builds its outgoing options by copying exactly those. Any other key reaches no branch
and is dropped — no throw, no warning. QueryParams also carries an index signature, so
the type system accepts both spellings equally.
The consequence is an unbounded read, not a truncated one: the platform's GET list
route has no default page size (objectstack packages/client/src/index.ts, pinned in
its client.test.ts), so an absent top returns the entire match set. A dropped cap is
therefore invisible until the object is large.
The four live sites
| file | call | dropped key |
|---|---|---|
apps/console/src/sdui-workbench-preview.tsx:65 |
find(objectName, { top: 200 }) |
top |
packages/app-shell/src/views/ObjectView.tsx:1561 |
find(…, { limit: 0 }) |
limit |
packages/app-shell/src/views/metadata-admin/AssignedUsersSection.tsx:109 |
find('sys_permission_set', { $filter: {…}, limit: 1 }) |
limit |
content/docs/guide/react-pages.md:129 |
find(…, { filters: [['status','=','open']] }) |
filters |
ObjectView.tsx:1561's limit: 0 is worth reading twice: $top: 0 is honoured end to
end as "no records" (objectstack #6485 pinned it), so if that call means what it says,
dropping the key inverts it from no rows to every row.
AssignedUsersSection.tsx:109 is a one-line-away neighbour of three correct calls
($top: 500, $top: 200, $top: 1000 on L124/131/136) — the shape a reviewer's eye
slides over.
sdui-workbench-preview.tsx:66 also reads all.records off the result, with no .data
arm. find() resolves to a QueryResult (data, never records), so that preview
lists nothing regardless of the cap. skills/objectui/guides/data-integration.md:268
publishes the same return result.records; to skill consumers.
Why a rule, and which one
eslint-rules/no-query-params-under-options.js already gates the sibling shape —
find(obj, { options: { $top: 100 } }) — and its own header makes the argument for
mechanising this family: the mistake type-checks, it publishes, the symptom looks like a
data problem rather than a code problem, "so a review catches it once and then misses the
next one." It cites two live instances by two authors (object-timeline,
objectui#4009 / objectstack#7137; object-kanban, objectui#4025).
The rule bans a $-prefixed key under options. It does not look at a bare key at
the top level, which is the other half of the same class — and the half with four live
sites. Extending it (or adding a sibling rule) is the cheap move: the AST walk already
lands on the params object literal of a find/findOne call, the fix is a second
predicate, and the contract lives in the same repo as the rule.
Suggested shape, narrow on purpose: flag a static key that is a known query-option
name without its $ (top, skip, filter, filters, select, orderby, sort,
expand, search, count, limit, offset) in the second argument of an
adapter.find / dataSource.find / …findOne call. Not "any unprefixed key" — adapters
legitimately take adapter-specific params, which is why the index signature exists.
An objectstack-side gate was considered for #10288 and rejected: QueryParams is
objectui's contract, so a checker there would have to hard-code another repo's key list
and become a second source of truth for it. The rule belongs next to the type it
enforces.
Related
- objectstack#10288 — the same two defects in
examples/app-showcase, fixed there. - objectstack#10469 — the same
.recordsmisread in objectstack's own react-pages guide. - objectui#4734 / objectstack#7147 — the
options: { $top }half, already gated.
Activity
os-zhuang commentedon Aug 21, 2026
Triage: moved to objectstack-ai/objectui#5458 (file-at-destination rule — every named site and the lint rule that should gate them live in objectui; transfer rebuilt with provenance header and qualified refs, queued there as Bug). Closing here as moved; this card carries no objectstack-side work.
Generated by Claude Code