Skip to content

Commit 492703e

Browse files
docs(objectql): query-syntax §9 states the all-unknown projection refusal, not the retired every-column fallback (#27586)
domain:devx#2 · os-justin · session_01QSeKYp3oFeDem8Tbh8pzWA Fixes #27565 Clause-②: no Metadata-protocol: none Skills-docs: updated here — content/docs/protocol/objectql/query-syntax.mdx §9 (heading, lead sentence, example and callout): the all-unknown projection is refused 400 VALIDATION_ERROR by engine.find() / findOne(), a mix still drops the unknown columns, expand sub-reads are unaffected, the REST ingress answers 400 INVALID_FIELD. Prose only; skills/** needs no change. ## 维护者速读 - **改了什么**:协议文档 `query-syntax.mdx` 第 9 节改写:投影里混有已知列时未知列才会被丢弃;全部是未知列时 `find()` / `findOne()` 答 `400 VALIDATION_ERROR`(与 `fields: []` 同一拒绝);`expand` 子查询不受影响;REST 入口对任何未知列答 `400 INVALID_FIELD`。 - **为什么改**:PR #27541 已让引擎拒绝全未知投影,但这页还写着"回退为读全部列",会把钩子和流程作者引向引擎现在拒绝的写法。 - **风险与回滚**:极低,纯文档,不动代码、不发布;回滚即 revert。 - **先看**:`content/docs/protocol/objectql/query-syntax.mdx` 第 9 节"Unknown Projection Fields"(唯一改动文件)。 - **两分钟验证**:`git grep -n "falls all the way back" content/docs`,预期无输出(退出码 1)。 - **需要你做的**:无,席位落地。 ## What changes `content/docs/protocol/objectql/query-syntax.mdx` §9. The heading "Unknown Fields Are Tolerated" became "Unknown Projection Fields", because the old one now misleads. The lead sentence, the example and the callout were rewritten, and the "falls all the way back to every field" clause is removed. `git grep -n "falls all the way back" content/docs` returns nothing (exit 1). No other page linked the old anchor (`git grep unknown-fields-are-tolerated` has no hits). Each clause was checked against the code and a test at head `6535c19c`: | Clause on the page | Code | Test | |:---|:---|:---| | An unknown plain column is dropped only while one requested column is known; `id` / `created_at` / `updated_at` always count as known | `packages/objectql/src/engine.ts:13635-13641` (find), `:13943-13946` (findOne); `packages/objectql/src/declared-read-columns.ts:79` | `packages/objectql/src/engine-empty-projection-refusal.test.ts:257`; `packages/objectql/src/query-expression-conformance.test.ts:1776` | | An all-unknown projection is refused by `find()` / `findOne()` with `400 VALIDATION_ERROR`, naming the requested columns, before any driver read | `engine.ts:13641`, `:13946` call `refuseProjectionOfUnknownColumns`; `packages/objectql/src/empty-projection-door.ts:61-78` (status, code, `got [...]` in the message) | `engine-empty-projection-refusal.test.ts:241`, `:249` (`reads` stays empty); `query-expression-conformance.test.ts:1786` | | The same refusal as `fields: []` | `empty-projection-door.ts:111` and `:125` both go through `refuseEmptyProjection` | the same file's `fields: []` block, earlier in `engine-empty-projection-refusal.test.ts` | | `expand` sub-reads are unaffected, because the engine always adds `id` and strips it back off | `engine.ts:12797`, `:12806` (adds `id`), `:12845` (strips it); `id` is never dropped (`declared-read-columns.ts:79`) | `packages/objectql/src/engine.test.ts:1942`, `:2010` | | The REST ingress answers `400 INVALID_FIELD` for any unknown name, alone or mixed | `packages/metadata-protocol/src/protocol-query-validation.ts:716-727` (`unknown.length > 0`) | `query-expression-conformance.test.ts:1431` (alone), `:1441` (mixed); `packages/rest/src/data-record-read-malformed-query-door.test.ts:73` | ## Tests <details><summary>The cited suites on the merged head 6535c19</summary> `bash scripts/pm/os-verify-lock.sh -c "pnpm --filter @objectstack/objectql exec vitest run --maxWorkers=2 src/engine-empty-projection-refusal.test.ts src/query-expression-conformance.test.ts"` ``` Test Files 2 passed (2) Tests 209 passed (209) os-verify-lock: VERDICT command-exit 0 ``` </details> ## Acceptance notes - Prose only, no code; `skip-changeset`: nothing in any package's `files[]` changes. - The old callout listed "registry-less hosts" among the callers the tolerance protects. That is dropped: an unregistered object is now refused `404 OBJECT_NOT_FOUND` by the engine first (`query-expression-conformance.test.ts`, the registry-less guard after `:1786`). The new lead sentence says "on a registered object". ## Verification <details><summary>Derived gates, local subset (head 6535c19)</summary> `node scripts/docs-audit/affected-docs.mjs`: exit 0, "0 docs name something this change touched". `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derived the same 43 commands before and after the `origin/main` merge. On the merged head all 43 exit 0. Some first answered exit 3 (PREREQUISITE NOT MET, nothing measured), or exit 1 from a stale `json-schema`, until the build closures of `@objectstack/lint...` and `@objectstack/client-react...` (spec included) were rebuilt on that head. After the rebuild they exit 0: `check:doc-formula-expressions`, `check:doc-security-posture`, `check:skill-examples`, `check:docs-transcript-drift`, `check:docs`. `--ran` with every exit recorded: "43 derived famil(ies) accounted for — 43 run, 0 NOT-MEASURED (a DERIVED zero — all 43 recorded an exit code and none of them is 3)". NOT MEASURED locally: `Build Docs` and `Check Documentation Links`. These are CI-only jobs, and this PR's check-runs answer them. </details> --- _Generated by [Claude Code](https://claude.ai/code/session_01QSeKYp3oFeDem8Tbh8pzWA)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent 4a00b2b commit 492703e

1 file changed

Lines changed: 20 additions & 10 deletions

File tree

‎content/docs/protocol/objectql/query-syntax.mdx‎

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1484,26 +1484,36 @@ const sorted = monthlyRevenue.sort((a, b) =>
14841484

14851485
## 9. Error Handling
14861486

1487-
### Unknown Fields Are Tolerated
1487+
### Unknown Projection Fields
14881488

1489-
Unknown field **names** are not rejected by `engine.find()` — a projected field that
1490-
doesn't exist on the object is **silently dropped** (matching OData / `SELECT *`
1491-
tolerance), so a stale field reference never fails the whole query:
1489+
On a registered object, `engine.find()` and `findOne()` drop an unknown plain column from
1490+
`fields` **only while at least one requested column is known**: the unknown name is
1491+
silently omitted from each row, so a stale field reference in a mixed projection does
1492+
not fail the whole query. `id`, `created_at` and `updated_at` always count as known.
14921493

14931494
```typescript
14941495
const rows = await engine.find('customer', {
14951496
fields: ['name', 'nonexistent'], // `nonexistent` is not on the schema
14961497
});
14971498
// Returns each row with `name`; `nonexistent` is omitted — no error thrown.
1499+
1500+
await engine.find('customer', { fields: ['nonexistent'] });
1501+
// Throws 400 VALIDATION_ERROR: no requested column is a field of 'customer'.
14981502
```
14991503

1504+
A projection whose **every** entry is unknown is refused: `find()` / `findOne()` throw
1505+
`400 VALIDATION_ERROR` naming the requested columns, before any driver is asked. It is
1506+
the same refusal `fields: []` gets, because a projection the tolerance empties is an
1507+
empty projection, and an empty projection is never read as every column.
1508+
15001509
<Callout type="info">
1501-
This tolerance does **not** extend to the REST/protocol ingress: a `select` / `fields`
1502-
naming a column the object does not have is `400 INVALID_FIELD` there, because dropping
1503-
it silently answered a *narrower* projection with a *wider* one — a projection left with
1504-
no known field falls all the way back to every field. The engine's tolerance guards
1505-
internal callers (hooks, flows, expand sub-reads, registry-less hosts) that never pass
1506-
through that ingress.
1510+
`expand` sub-reads are unaffected: the engine always adds `id` to a sub-read's
1511+
projection (and strips it from the expanded record when you did not ask for it), so a
1512+
sub-read never reaches the all-unknown refusal.
1513+
1514+
The REST/protocol ingress tolerates no unknown name at all: a `select` / `fields`
1515+
naming any column the object does not have, alone or next to known ones, is
1516+
`400 INVALID_FIELD` there.
15071517
</Callout>
15081518

15091519
### Security Violations

0 commit comments

Comments
 (0)