@@ -9,41 +9,47 @@ import { strictObject } from '../shared/strict-object';
99
1010/**
1111 * Sort Node
12- * Represents "Order By" — one `{ field, order }` pair.
13- *
14- * **Closed against unknown keys (#4721, #4001).** This is the one site in
15- * `query.zod.ts` carved out of the file's blanket `open` classification, and the
16- * carve-out is what the shape earns: the rest of the file is the query DIALECT,
17- * where user data flows through predicate values, while a sort node is a closed
18- * two-key tuple with no user-data face at all. Classing the whole file was the
19- * imprecise thing, not closing this schema.
20- *
21- * What it stops, measured on `main` before the change:
22- *
23- * ```
24- * SortNodeSchema.parse({ field: 'updated_at', direction: 'desc' })
25- * → { field: 'updated_at', order: 'asc' }
26- * ```
27- *
28- * `direction` was stripped, `order` fell back to its `asc` default, and the sort
29- * ran in the OPPOSITE direction under an ordinary success. Paired with `limit` —
30- * which is how a caller asks for "the latest N" — that is not a reordered page
31- * but a DIFFERENT SET OF ROWS, with no signal anywhere in the response.
32- *
33- * `direction` gets a named alias rather than a distance-based suggestion because
34- * it is not a typo: it is `IReportService.orderBy`'s live vocabulary
35- * (`contracts/report-service.ts`), a genuinely different contract that
36- * `plugin-auth/objectql-adapter.ts` already translates by hand. Edit distance
37- * can never reach a different WORD for the same intent — the `visibleWhen →
38- * visible` class (see `shared/strict-object.ts`) — so only a hand-written entry
39- * puts the prescription in the author's hands.
40- *
41- * The wire-facing half of the same door is `normalizeSortNodes`
42- * (`metadata-protocol/src/protocol.ts`), which rejects `direction` by name with
43- * `400 INVALID_SORT` before a request ever reaches this schema. Both were closed
44- * in one change deliberately: closing only the schema is the door asymmetry
45- * #1535 shipped and #4522 had to come back for.
12+ * Represents "Order By" — one `{ field, order }` pair. Unknown keys are
13+ * REJECTED (#4721); spell the direction `order`, never `direction`.
4614 */
15+ // ⚠️ Keep the block above short: `build-docs.ts` takes the FIRST JSDoc block in
16+ // the file as this page's description, so the rationale below is line comments.
17+ //
18+ // ─── Why this one schema is strict while the rest of the file is not (#4721) ──
19+ //
20+ // `query.zod.ts` is classed `open` in the #4001 strictness ledger, and
21+ // `SortNodeSchema` is carved out of that blanket. The carve-out is what the
22+ // shape earns: the rest of the file is the query DIALECT, where user data flows
23+ // through predicate values, while a sort node is a closed two-key tuple with no
24+ // user-data face at all. Classing by FILE was the imprecise instrument here, not
25+ // closing this schema.
26+ //
27+ // What the closure stops, measured on `main` before the change:
28+ //
29+ // SortNodeSchema.parse({ field: 'updated_at', direction: 'desc' })
30+ // → { field: 'updated_at', order: 'asc' }
31+ //
32+ // `direction` was stripped, `order` fell back to its `asc` default, and the sort
33+ // ran in the OPPOSITE direction under an ordinary success. Paired with `limit` —
34+ // which is how a caller asks for "the latest N" — that is not a reordered page
35+ // but a DIFFERENT SET OF ROWS, with no signal anywhere in the response.
36+ //
37+ // `direction` gets a named alias rather than a distance-based suggestion because
38+ // it is not a typo: it is `IReportService.orderBy`'s live vocabulary
39+ // (`contracts/report-service.ts`), a genuinely different contract that
40+ // `plugin-auth/objectql-adapter.ts` already translates by hand. Edit distance
41+ // can never reach a different WORD for the same intent — the `visibleWhen →
42+ // visible` class (see `shared/strict-object.ts`) — so only a hand-written entry
43+ // puts the prescription in the author's hands.
44+ //
45+ // The wire-facing half of the same door is `normalizeSortNodes`
46+ // (`metadata-protocol/src/protocol.ts`), which rejects `direction` by name with
47+ // `400 INVALID_SORT` before a request ever reaches this schema. Both were closed
48+ // in one change deliberately: closing only the schema is the door asymmetry
49+ // #1535 shipped and #4522 had to come back for.
50+ //
51+ // Deliberately NOT taken here: `BaseQuerySchema`'s own top level stays
52+ // non-strict. That is #4001's to schedule.
4753export const SortNodeSchema = lazySchema ( ( ) => strictObject (
4854 {
4955 surface : 'this sort node' ,
0 commit comments