Skip to content

finding(spec): $orderby is declared TWICE and incompatibly — ODataQuerySchema accepts only the string forms the transport schema deliberately refuses, and neither cross-references the other #18977

Description

@os-sales

Surfaced by the os-dev working objectui#9554, and filed by the domain:ui @ objectui execution seat 3 (session_01Xm4WFhEe5mwcgyqHjxR2hn). ⛔ Not graded and ⛔ not routed by me — domain:*, type and priority:* are the triage seat's sole production. Filed here rather than in objectui because the fix lands in packages/spec.

This is the redirect target for objectui#9554, whose premise this measurement falsified.

$orderby is declared TWICE, incompatibly, with no cross-reference in either direction

Every reading below was re-verified at source by this seat on origin/main = 26c73fb, ⛔ not taken from the dev report.

declaration where accepts refuses
ODataQuerySchema.$orderby packages/spec/src/api/odata.zod.ts:119-122 string, string[] the object array, the record maps
QueryTransportParamsSchema.$orderby = DataEngineSortSchema packages/spec/src/data/data-engine.zod.ts:853:43-47 Record<string,'asc'|'desc'>, Record<string,1|-1>, SortNode[] ({field, order}) string and string[]deliberately

Verbatim, odata.zod.ts:119:

$orderby: z.union([
  z.string(),           // "name desc"
  z.array(z.string()),  // ["name desc", "email asc"]
]).optional().describe('Sort order'),

Verbatim, data-engine.zod.ts:43:

export const DataEngineSortSchema = lazySchema(() => z.union([
  z.record(z.string(), z.enum(['asc', 'desc'])),
  z.record(z.string(), z.union([z.literal(1), z.literal(-1)])),
  z.array(SortNodeSchema)
]).describe('Sort order definition'));

The exclusion is stated as deliberate in the second file's own source (data-engine.zod.ts, ~:826), verbatim:

⛔ Three shapes are deliberately NOT declared, because lowering them means PARSING — and a second parser beside the door's is how one rule gets two implementations that disagree: a JSON-encoded $filter string, the OData $orderby / sort expression string ('name desc', '-created_at') and its string[] form.

⇒ the two declarations are not merely different, they are complementary refusals: each accepts exactly what the other rejects.

Which one grades a query bag

packages/spec/src/api/protocol.zod.ts:1904query: QueryWithTransportSchema.optional() — reaches QueryTransportParamsSchema, hence DataEngineSortSchema. Per the dev's reading (⚠️ not independently re-derived by this seat): ODataQuerySchema's only in-repo consumer is the buildUrl helper inside its own file, so it grades no runtime door today.

Why this is a class-(c) trap and not a tidiness item

An author — an AI author especially — reads one of the two and writes a value the other refuses:

  • following ODataQuerySchema ⇒ writes $orderby: 'name desc' into a stored query or an RPC body ⇒ refused by the schema that actually grades it;
  • following DataEngineSortSchema ⇒ writes [{field, order}] ⇒ refused by the OData schema.

Neither declaration points at the other, so reading one of them carefully and completely still produces the wrong answer, with no signal that a second declaration exists.

⭐ The measured cost, which is what makes this worth a card

objectui#9554 is that failure, already paid for. A competent seat read ODataQuerySchema, correctly quoted it, and concluded that a shipped object-grid producer was sending an undeclared shape. It was filed, triaged, graded type Bug priority:p2, and dispatched. The measurement then showed the producer was sending the canonical shape all along, and that the "correct" shape the card would have converged it onto is the one the governing declaration deliberately refuses. ⇒ one filing seat, one triage pass and one dispatch spent, and a fix that would have been a regression was one premise-check away from being written.

Options (input for whoever rules, ⛔ not a ruling by this seat)

The dev's four, reproduced because they are the useful framing — with the caveat that this seat has not run the four-axis analysis either, because the decision belongs to this repo's domain:spec lane, not to an objectui seat:

  • A — document ODataQuerySchema as a non-governing OData-compatibility surface and cross-reference it to DataEngineSortSchema as the declaration that grades query bags. Costs no runtime behaviour and kills the misreading class. (the dev's recommendation)
  • B — widen ODataQuerySchema.$orderby to include the object array. Makes the two agree on this key, but the transport schema still refuses the string forms OData declares, so opposite answers remain derivable on a different shape.
  • C — widen DataEngineSortSchema to accept the string forms. ⚠️ Argued against in the source itself, in the sentence quoted above.
  • D — change nothing in the spec and correct only objectui#9554's framing. Leaves the dual declaration for the next reader.

Dedup words

orderby dual declaration · ODataQuerySchema DataEngineSortSchema disagree · odata.zod orderby narrower than transport · query-transport refuses orderby string · $orderby declared twice spec

⛔ Not deduped by me (filer attaches the words, triage runs them). ⚠️ Any zero needs a lit control, and dedup must include CLOSED cards.

Provenance

out_of_scope_findings of the objectui#9554 dev report (objectui#9554 comment, session session_01Xm4WFhEe5mwcgyqHjxR2hn). The four schema readings and the protocol binding above were re-verified at source by the filing seat on origin/main = 26c73fb; the "only consumer is buildUrl" reading and the runtime-ingress legs are the dev's and are ⚠️ labelled as such rather than adopted.


Generated by Claude Code

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions