Observation-class finding, split out of #6877 while implementing it. Unassigned, filed per Prime Directive #10. #6877's PR deliberately did not widen into this — it is in a different package and a different owner's contract.
Why it is a separate card and not part of #6877
#6877 swept packages/rest/src/rest-server.ts's query read points and declared, per handler, which parameters are single-valued. One route was deliberately left ungated: GET /api/v1/data/:object does not read parameters at all — it hands the WHOLE query record through:
const result = await p.findData({
object: req.params.object,
query: req.query,
...
});
Every parameter's arity for that route is therefore decided by the shared list-query normalizer in packages/metadata-protocol/src/protocol.ts, not by packages/rest. Declaring an arity list at the REST layer would have been one package guessing at another's contract, and the normalizer is genuinely the right home: GET /data/:object, POST /data/:object/query and the runtime dispatcher all flow through it, which is exactly the reason #4181's filter rejection was put there rather than copied per route.
The fact
IHttpRequest.query is Record< string, string | string[] > and the array arm is produced by a real first-party adapter — NodeHttpServer hands ?x=1&x=2 through as ['1','2'], measured over a socket on #6878. The normalizer coerces without checking the arity it was handed. Read directly, packages/metadata-protocol/src/protocol.ts:
if (options.limit != null) options.limit = Number(options.limit);
if (options.offset != null) options.offset = Number(options.offset);
Number(['1','2']) is NaN, so ?$top=1&$top=2 reaches the driver as limit: NaN — the same shape #6928 / PR #7299 just fixed one layer over on GET /api/v1/notifications, where NaN survived the clamp and landed in data.find({ limit: NaN }).
That is one measured line, not a survey. The survey is the work: this normalizer also folds four spellings of the filter slot, a large alias table (pageSize / perPage / take / first / … all rewrite to $top), the $-alias consumption pass, and the leftover-key bucket that lowers unknown keys into field-equality predicates. Each of those needs the same per-parameter single-vs-multi judgement #6877 made for the REST layer, and some of them are genuinely multi-valued ($select, $expand, $searchFields all already accept the array arm on purpose).
Not live today, and exactly why that is temporary
No user hits this now: it takes a client that repeats a parameter, and the production Hono adapter collapses repeats to the first value before any handler runs. #6878's route 2 — ruled adopted on 2026-08-10 — removes that collapse. So the dormancy here has the same expiry date the #6877 surface had, and the same reasoning applies: this is the prerequisite for a decided change, not speculative hardening.
Not filed as a sub-issue of #6877: its fix lands outside #6877's completion scope (different package, different contract owner). Depends on nothing, but its priority is coupled to #6878 route 2.
Dedup
Searched open issues for req.query, findData + normalizer, metadata-protocol + query param, and string[]. Hits: #6307 (the origin, two handlers in package-routes.ts), #6877 (the packages/rest sweep, this card's parent in origin only), #6878 (the adapter divergence). None covers the findData normalizer.
Generated by Claude Code
Observation-class finding, split out of #6877 while implementing it. Unassigned, filed per Prime Directive #10. #6877's PR deliberately did not widen into this — it is in a different package and a different owner's contract.
Why it is a separate card and not part of #6877
#6877 swept
packages/rest/src/rest-server.ts's query read points and declared, per handler, which parameters are single-valued. One route was deliberately left ungated:GET /api/v1/data/:objectdoes not read parameters at all — it hands the WHOLE query record through:Every parameter's arity for that route is therefore decided by the shared list-query normalizer in
packages/metadata-protocol/src/protocol.ts, not bypackages/rest. Declaring an arity list at the REST layer would have been one package guessing at another's contract, and the normalizer is genuinely the right home:GET /data/:object,POST /data/:object/queryand the runtime dispatcher all flow through it, which is exactly the reason #4181's filter rejection was put there rather than copied per route.The fact
IHttpRequest.queryisRecord< string, string | string[] >and the array arm is produced by a real first-party adapter —NodeHttpServerhands?x=1&x=2through as['1','2'], measured over a socket on #6878. The normalizer coerces without checking the arity it was handed. Read directly,packages/metadata-protocol/src/protocol.ts:Number(['1','2'])isNaN, so?$top=1&$top=2reaches the driver aslimit: NaN— the same shape #6928 / PR #7299 just fixed one layer over onGET /api/v1/notifications, whereNaNsurvived the clamp and landed indata.find({ limit: NaN }).That is one measured line, not a survey. The survey is the work: this normalizer also folds four spellings of the filter slot, a large alias table (
pageSize/perPage/take/first/ … all rewrite to$top), the$-alias consumption pass, and the leftover-key bucket that lowers unknown keys into field-equality predicates. Each of those needs the same per-parameter single-vs-multi judgement #6877 made for the REST layer, and some of them are genuinely multi-valued ($select,$expand,$searchFieldsall already accept the array arm on purpose).Not live today, and exactly why that is temporary
No user hits this now: it takes a client that repeats a parameter, and the production Hono adapter collapses repeats to the first value before any handler runs. #6878's route 2 — ruled adopted on 2026-08-10 — removes that collapse. So the dormancy here has the same expiry date the #6877 surface had, and the same reasoning applies: this is the prerequisite for a decided change, not speculative hardening.
Not filed as a sub-issue of #6877: its fix lands outside #6877's completion scope (different package, different contract owner). Depends on nothing, but its priority is coupled to #6878 route 2.
Dedup
Searched open issues for
req.query,findData+ normalizer,metadata-protocol+ query param, andstring[]. Hits: #6307 (the origin, two handlers inpackage-routes.ts), #6877 (thepackages/restsweep, this card's parent in origin only), #6878 (the adapter divergence). None covers thefindDatanormalizer.Generated by Claude Code