Skip to content

Data query: an unknown field inside where / $filter answers 200/0 instead of 400 INVALID_FIELD — the bare-key door disagrees (#4134's uncovered sibling) #7534

Description

@huangyiirene

Symptom

POST /api/v1/data/showcase_invoice/query with {"where":{"not_a_field":"x"}} answers:

200 { "records": [], "total": 0 }

— no code, no mention of the unknown name. Identically for the $filter door (GET ...?$filter=...) and for the filter-AST door (POST {filter: [...]}).

Expected: 400 INVALID_FIELD naming the field, the same envelope the sibling door already produces.

Same-run control on the same object, same field name: GET /api/v1/data/showcase_invoice?not_a_field=x400 INVALID_FIELD.

So one endpoint family answers two different verdicts for one mistake, chosen by which door the caller used — and the losing answer is "indistinguishable from no data", which is exactly what #4134 closed for the bare-key door: an empty result set that a client cannot tell apart from a filter that legitimately matched nothing.

Root cause

The #4134 read-path field-existence gate (assertQueryParamsAreFields, backed by resolveQueryFields, in packages/metadata-protocol/src/protocol.ts) covers only the implicit filters findData derives from leftover query parameters. The explicit filter axes — a where object, a $filter string, a filter AST — reach the driver without passing that gate, so an unknown key is lowered into a field-equality predicate that can only match zero rows.

Relationship to #4134 (closed/completed, fixed by PR #4169): this is not a regression of it — the bare-key control still answers 400 INVALID_FIELD in this run. It is the sibling door #4134's fix did not reach, and the gate it introduced (resolveQueryFields, explicitly documented as "ONE resolution shared by all four read axes") is the natural place for the fix to land.

Reproduction

  1. Boot showcase on a fresh isolated file DB (SqlDriver / better-sqlite3); authenticate as admin@objectos.ai.
  2. POST /api/v1/data/showcase_invoice/query body {"where":{"not_a_field":"x"}}200 {"records":[],"total":0}, no code.
  3. Repeat through the $filter door and the AST (filter) door — same 200/0.
  4. Control, same run and same object: GET /api/v1/data/showcase_invoice?not_a_field=x400 INVALID_FIELD.

Source

Extracted from the QA run #7463 (framework a86db17).

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions