Skip to content

[rest] GET /data/:object/:id folds no query aliases — the CANONICAL fields spelling is dropped while the alias select works #8039

Description

@hotlong

Found while implementing #7606 (measuring that route's closed query-parameter set). Filed unassigned per Prime Directive #10; not fixed there, because widening a route's accepted spellings is surface expansion, not the recognition policy #7606 landed.

The condition

packages/spec/src/data/data-engine.zod.ts declares the alias table (RPC_QUERY_ALIAS_SLOTS) — "the ONE place the alias → canonical mapping is declared":

slotcanonicalalias
fieldsfieldsselect
expandexpandpopulate

GET /data/:object/:id reads exactly one line off the query string:

const{ select, expand }=req.query||{};

So it honours the alias of the fields slot (select) and the canonical of the expand slot (expand), and folds nothing. ?fields=title and ?populate=owner never reach getData.

The asymmetry is per-slot and reads as arbitrary from outside: the same caller writing ?fields= and ?expand= gets one of the two honoured.

Why it mattered before #7606, and what changed

Until #7606 the drop was silent: ?fields=title returned the FULL record with a 200, indistinguishable from a caller who asked for everything — the exact silent-widening shape that card exists for.

Since #7606 the route declares a closed set (DATA_RECORD_READ_PARAMS = ['select', 'expand']) and both spellings are now refused with a located 400 naming select / expand as what the route accepts. So the failure is no longer silent, and this is no longer a correctness hole — it is an inconsistency with the declared alias table.

The two spellings were deliberately left OUTSIDE the closed set rather than added to it: putting them in the allowlist without implementing the fold would advertise a capability the handler does not have (declared ≠ enforced, Prime Directive #10), which is worse than refusing.

The sibling route already folds

GET /data/:object (list) passes its whole query to the metadata-protocol normalizer, which folds by the same table, extended with the wire-only spellings (filters, $filter, $expand). So ?fields= works on the list route and is refused on the by-id route.

The options

  1. Fold the table on the by-id route — read the slots through foldQueryAliasSlots (or the normalizer) and add the folded spellings to the closed set. One rule, declared in one place, both routes agreeing. Costs an alias-conflict answer (?fields=a&select=b) that the route does not have today.
  2. Leave it. The refusal is located and self-reporting, and select / expand are the documented spellings for this route. The table then describes the list route's ingress, not the whole data surface.
  3. Retire the divergence from the other end — decide the by-id route's two names ARE the contract and note in the table that it is not universal.

Not obviously worth paying for; recording it so the next person to touch either the alias table or this handler is choosing rather than discovering.

Dedup

Searched open issues for the alias-table identifier, for select/fields/populate spelling wording, and for by-id read-route query handling. No open card covers it; #7606 is the parent this came out of, #8001 is the sibling recording a different cross-route divergence on the same file.


Generated by Claude Code

Metadata

Metadata

Assignees

Labels

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions