Skip to content

docs(api): sort spellings are slot-specific, not equivalent across routes - #19055

Merged
os-try-charles merged 1 commit into
mainfrom
claude/issue-19027-data-api-orderby-spellings-not-equivalent
Sep 18, 2026
Merged

os-try-charles merged 1 commit into
mainfrom
claude/issue-19027-data-api-orderby-spellings-not-equivalent

Conversation

@os-try-charles

Copy link
Copy Markdown
Collaborator

Fixes #19027

Clause-②: no

What the page said, and what the runtime does

content/docs/api/data-api.mdx taught three POST /data/:object/query sort
spellings as "all equivalent". Reproduced at the exact input rest-server.ts
builds — FindDataRequestSchema.safeParse({ object, query: { ...body, object } })
— and again over real HTTP against a booted examples/app-crm:

documented bag schema-level over HTTP
{"orderBy": [{"field": "created_at", "order": "desc"}]} ⭐ lit control success: true 200, rows descending
{"orderBy": ["-created_at"]} query.orderBy.0 · invalid_type · "expected object, received string" 400 VALIDATION_FAILED
{"orderBy": {"created_at": "desc"}} query.orderBy · invalid_type · "expected array, received object" 400 VALIDATION_FAILED

The lit control returns 200, so the instrument is sound and the defect is the
page's, as the card diagnosed.

Where the card's diagnosis needed correcting

The card says the record map and the shorthand array "are transport-slot values
— they have to arrive on $orderby / sort". Measured, that holds for the map
and not for the shorthand array. Every POST-body slot, one tree:

body value orderBy $orderby sort
[{"field": "created_at", "order": "desc"}] 200 200 200
{"created_at": "desc"} (and 1 / -1) 400 200 200
["-created_at"] 400 400 400
"-created_at" (bare string) 400 400 400

QueryTransportParamsSchema.$orderby is DataEngineSortSchema, which declares
the record map and SortNode[] and no string form at all. So the shorthand
string is not a transport-slot value on this route either — it is a
querystring spelling, read by normalizeSortNodes at the ingress. Measured
on the querystring: ?sort=-created_at, ?$orderby=-created_at and
?orderBy=-created_at all sort; the two JSON shapes sent there are
400 INVALID_SORT, because that route parses no JSON in this slot and reads the
whole value as one field name.

Had the page simply been trimmed to the card's diagnosis, a reader sent to
{"$orderby": ["-created_at"]} would have got the same 400 from a different
line.

The fix

The sentence is replaced by a per-route, per-slot table plus two short
paragraphs. Every cell in it is a measurement, not a reading of the schema.
The page's only mention of POST /data/:object/query was inside the sentence
being replaced, so the table is also now that route's only description here.

The status-code split is stated per route as well: a wrong shape in a body is
400 VALIDATION_FAILED with the path in fields (query.orderBy.0,
query.orderBy.0.order) and never reaches the field check, while an unknown
field name is 400 INVALID_SORT on either route.

Verification

  • Reproduction, schema level — the three bags at the rest-server input, plus
    the full slot matrix above and the malformed-shape cases.
  • Reproduction, HTTP level — pnpm dev:crm -- --fresh -p PORT (a random high port), signed in
    as the seeded dev admin, GET /data/crm_account and
    POST /data/crm_account/query. Ascending/descending row order is the lit
    control on every 200 cell: ['Acme Corp','Globex Ltd','Initech'] vs
    ['Initech','Globex Ltd','Acme Corp'], so an accepted-but-unapplied sort
    could not read as a pass.
  • Post-condition probe, written before the edit — it parses the new table
    and asserts each cell's verdict token equals the measured one, that the four
    spellings are still shown (so deleting them would fail it), and that no
    cross-route equivalence wording survives. It reports 8 failures against the
    pre-edit page and passes against the edited one; both legs were run with the
    same scoped instrument.
  • Derived gates — node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, 39 commands, all run; exits in the report.

No changeset — measured, not asserted

No published package contains content/docs/api/data-api.mdx: over the 70
non-private manifests in the tree, zero sit at or above that path. Positive
control, same method: packages/spec/dist/index.js resolves into
@objectstack/spec, whose files[] ships dist. apps/docs, which renders
this tree, is private: true. So nothing published moves and the change takes
skip-changeset.

Acceptance notes

  • #18977 is not addressed here, and PR #19018 is untouched by this diff —
    this PR edits one file, that one edits four, and they do not overlap. No
    packages/spec/** path is in this diff: the measurement showed the defect is
    the documentation's, which is the branch the dispatch reserved for staying on
    this page.
  • Noted, not filed: packages/spec/src/api/odata.zod.ts already carries the
    correct account of this split in a schema comment (the #18977
    cross-reference), including the sentence that the string forms are served by
    normalizeSortNodes and not by the body door. The customer-facing page had
    no pointer to it in either direction. Successor: the next PR to touch that
    schema comment or this page.

🤖 Generated with Claude Code

https://claude.ai/code/session_017ef78bLdybu3AffehKkhfk


Generated by Claude Code

…utes

`content/docs/api/data-api.mdx` taught three `POST /data/:object/query` sort
spellings as "all equivalent". Measured over real HTTP on both routes, two of
the three answer `400 VALIDATION_FAILED` on `orderBy`:

  {"orderBy": [{"field":"created_at","order":"desc"}]}  200
  {"orderBy": ["-created_at"]}                          400 @ query.orderBy.0
  {"orderBy": {"created_at":"desc"}}                     400 @ query.orderBy

The record map is a transport-slot value — it works on `$orderby` / `sort` in
the same body. The shorthand string, bare or inside an array, reaches no body
slot at all; it is a querystring spelling, served by `normalizeSortNodes` at
the ingress.

Replaces the sentence with a per-route, per-slot table whose every cell was
measured, on a booted CRM app, on both routes.

Claude-Session: https://claude.ai/code/session_017ef78bLdybu3AffehKkhfk
Co-authored-by: Claude <noreply@anthropic.com>
@os-try-charles os-try-charles added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 18, 2026 — with Claude
@github-actions github-actions Bot added the documentation Improvements or additions to documentation label Sep 18, 2026
@os-try-charles
os-try-charles marked this pull request as ready for review September 18, 2026 14:54
@os-try-charles
os-try-charles added this pull request to the merge queue Sep 18, 2026
Merged via the queue into main with commit 54818fe Sep 18, 2026
38 checks passed
@os-try-charles
os-try-charles deleted the claude/issue-19027-data-api-orderby-spellings-not-equivalent branch September 18, 2026 15:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/s skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants