Repository navigation
Commit 49d652e
fix(spec)!: EngineQueryOptionsSchema.fields and the RPC request's select refuse an empty projection, as their transport siblings do (#27007)
<!-- claude-projects-attribution: {"github_login":"os-support-ai"} -->
_Requested by **Yibo Wang** · [project
thread](https://claude.ai/code/project/chan_01BVUyyPie8gHwoPM18iABwB?thread=cmsg_01BVUyyPie8gHwoPM18iABwBSHirnFJasE7wztBSe558R2)_
Fixes #26954
Clause-②: no (narrowing)
Metadata-protocol: none
Skills-docs: none — the skills teach only a non-empty engine `fields`
projection (`skills/objectstack-query/rules/filters.md:105`, `fields:
['id']`; `skills/objectstack-query/SKILL.md:25` lists `fields` among the
legal `find` option keys) and never an empty one, so no skill sentence
goes false; the one transport data-read line,
`skills/objectui/guides/data-integration.md:82` (`$select: ['name',
'email']`), stays true too.
## 维护者速读
- 改了什么:引擎侧四份「选哪些字段」的公开声明(`EngineQueryOptionsSchema.fields`、RPC 请求里已弃用的
`select`、旧版 `DataEngineQueryOptionsSchema.select`、向量检索请求的
`fields`)不再接受空数组和带空白名的数组,与 #26949 收窄的三份兄弟声明共用同一条规则(同一处定义,未复制)。
- 为什么改:兄弟声明已拒绝空投影,这四份仍写着「空也收」,按声明生成的客户端或 AI 作者会学到相反的规则。
- 风险与回滚:仓内没有任何运行时用这四份 schema 解析请求,线上请求零变化;受影响的只有仓外直接用这些导出 schema
解析、且发了空投影的代码。进程内 `engine.find(obj, { fields: [] })` 仍读全部列,不变。回滚 = revert
本 PR。
- 先看:`packages/spec/src/data/data-engine.zod.ts`(共享规则上移到
`EngineQueryOptionsSchema`
之前,以及四个成员)、`packages/spec/src/data/data-engine.test.ts`(新 describe
块)、`.changeset/26954-engine-projection-empty.md`。
- 两分钟验证:`pnpm --filter @objectstack/spec exec vitest run
src/data/data-engine.test.ts -t 'engine-side projection'`,预期 44
个用例全绿:空数组与空白名被拒且报错文字与兄弟声明一致,非空列表通过。
- 需要你做的:无,席位落地。
## What changes
- `packages/spec/src/data/data-engine.zod.ts`:
- The projection rule block from #26949 (`PROJECTION_NAMES_NO_FIELD`,
`TransportProjectionStringSchema`, `transportProjectionArray`,
`PROJECTION_ACCEPT_SET` and its docblock) **moves** from just above the
page-window block to just above `EngineQueryOptionsSchema`. It is not
copied. It has to move: `RpcQueryOptionsSchema` is a non-lazy
`EngineQueryOptionsSchema.extend(...)`, so importing the module runs the
lazy factory before any later `const` is initialised (a TDZ
`ReferenceError`). The docblock gains one paragraph that says so and
names the four new readers.
- `EngineQueryOptionsSchema.fields`: `z.array(FieldNodeSchema)` becomes
`transportProjectionArray(FieldNodeSchema)`, the same construction as
`QueryTransportParamsSchema.$select`.
- `RpcQueryOptionsSchema.select` (`@deprecated`): `z.array(z.string())`
becomes `transportProjectionArray(z.string())`, the same construction as
`QueryTransportParamsSchema.select` and the arm
`GetDataRequestSchema.select` reads.
- Folded in at the seat's request (same file, same family, same ruling):
`DataEngineQueryOptionsSchema.select` (`@deprecated` legacy options) and
`DataEngineVectorFindRequestSchema.fields` (the `vectorFind` arm of
`DataEngineRequestSchema`). Both change from `z.array(z.string())` to
`transportProjectionArray(z.string())`.
- `packages/spec/src/data/data-engine.test.ts`: one new `describe` block
(44 cases), described under Tests.
- `.changeset/26954-engine-projection-empty.md`: `@objectstack/spec`
patch, with both declaration lines, one ADR-0087 marker and a BREAKING
banner with Before / Now / Unchanged.
No `.describe()` changes. None of the four members had one, and
`check:generated` reports all 14 artifacts up to date on the head, so no
reference page regenerates. A sentence saying "is refused" would also be
false of the in-process engine (see Acceptance notes).
**The ADR-0049 note** on `DataEngineRequestSchema` ("every arm below now
has zero readers in this repo … an ADR-0049 enforce-or-remove
candidate") is unchanged. I re-measured it on this head: no code outside
`packages/spec` parses `DataEngineRequestSchema`,
`DataEngineFindRequestSchema`, `DataEngineFindOneRequestSchema` or
`DataEngineContractSchema` (`git grep`, non-test: the hits outside
`data-engine.zod.ts` are the generated reference page's import line, the
export ledgers and CHANGELOG prose). So every sentence in it is still
true. The note moved down by 33 lines with the block.
## Why
After #26817 and #26909, every published transport declaration of a
read's projection refuses an empty one. These four engine-side
declarations still admitted `[]` and blank names, so a client generated
from their JSON Schema, or an AI author reading them, learned the
opposite rule. Seat ruling on #26954: align, do not retire.
## Readers and producers (measured on `origin/main` 3217fc5)
- **Runtime parsers:** none, for any of the four members.
`EngineQueryOptionsSchema` is read at runtime only for its key list
(`runtime/src/action-execution.ts:2047`, `.shape` keys) and by
objectql's `engine-unknown-option.test.ts` (keys). Key sets do not
change. `RpcQueryOptionsSchema` is module-private and reached only
through the three request schemas above, which have no parser outside
spec. `DataEngineQueryOptionsSchema` and
`DataEngineVectorFindRequestSchema` have no reference outside
`packages/spec` except the generated reference page's import line and
one prose sentence in
`content/docs/kernel/contracts/data-engine.mdx:637`, which names the
legacy schema as deprecated and is still true.
- **In-process engine:** `ObjectQL.find` / `findOne` never parse their
options with this schema. `engine.ts:13563` projects only when
`ast.fields.length > 0`, and `:13573` falls back to `*` on an empty
projection. So `engine.find(obj, { fields: [] })` reads every column, as
before.
- **Types:** `z.input` / `z.infer` stay `string[]` for all four members,
so no TypeScript caller changes. `pnpm --filter @objectstack/spec
typecheck` passes on the head.
- **Client SDK / codegen / OpenAPI:** `packages/client` imports none of
these schemas. JSON Schema gains `minItems: 1` and a per-item `\S`
pattern. Both are a `min_length` and a `string_format`, not `custom`, so
there is no `dropped-refinements` row. `api-surface`, `declaration-map`,
`export-origins` and `authorable-surface` are key- or export-level and
unchanged (`check:generated`: 14/14 up to date). `gen:openapi` is
ungated.
- **In-repo callers sending `[]`:** none. `git grep` over `packages`,
`ui`, `examples`, `apps` and `skills` (non-test) for `fields: []` /
`select: []`: every hit is UI, flow, analytics or result metadata, not
an engine query.
- **Stored or authored data:** none of the four members is authorable
metadata. No metadata type embeds `EngineQueryOptionsSchema`,
`DataEngineQueryOptionsSchema` or the request schemas (`git grep` over
`packages/spec/src`). The authored projection is `QuerySchema.fields`
(views, reports), which this PR does not touch. So `Metadata-protocol:
none`, and ADR-0087 is `not-required (no-migration-prescription)`.
## Tests
`packages/spec/src/data/data-engine.test.ts`, `describe('the engine-side
projection declarations admit exactly what their transport siblings
admit')`:
- `EngineQueryOptionsSchema.fields` refuses `[]`, `['name', '']` and
`['name', ' ']`. The issue path is `['fields']` or `['fields', 1]`. The
message equals the one `QueryTransportParamsSchema.$select` gives for
the same value, and that sibling's path is asserted too. It keeps
`['name']` and `['name', 'email']` unchanged.
- For each of `DataEngineFindRequestSchema`,
`DataEngineFindOneRequestSchema` and the `DataEngineRequestSchema`
union:
- `query.select` refuses the same three values at `['query', 'select',
…]`, with the bare `select` sibling's message;
- `query.fields` refuses them at `['query', 'fields', …]`, with
`$select`'s message;
- `query.select: ['name']` / `['name', 'email']` parses and folds onto
`fields`.
- `DataEngineQueryOptionsSchema.select` refuses the same three values at
`['select', …]`, with the bare `select` sibling's message, and keeps a
non-empty list unchanged.
- `DataEngineVectorFindRequestSchema` and the `DataEngineRequestSchema`
union (`method: 'vectorFind'`): `fields` refuses the three values at
`['fields', …]`, with the bare `select` sibling's message, and keeps a
non-empty list unchanged.
Ablation, after committing the fix, through
`scripts/ablation-replace.mjs` (anchor hit, blob moved, restored to the
HEAD blob with `git diff HEAD` empty):
- `fields` back to `z.array(FieldNodeSchema)`: 12 failed / 17 passed in
the block.
- `select` back to `z.array(z.string())`: 9 failed / 20 passed.
- On `ba848653` (the 44-case block):
`DataEngineQueryOptionsSchema.select` reverted gives 3 failed / 41
passed; `DataEngineVectorFindRequestSchema.fields` reverted gives 6
failed / 38 passed. Both restored to the HEAD blob, with `git diff HEAD`
empty.
Runs on head `ba848653`:
- `@objectstack/spec` full suite under the lock: 819 files, 23498 tests
passed (1 todo). Then `typecheck` (tsc, scripts and test typecheck):
`VERDICT command-exit 0`.
- `pnpm --filter @objectstack/spec build && check:generated`: `VERDICT
command-exit 0`, and all 14 generated artifacts are up to date, so
nothing was regenerated.
- Full dogfood, owed by the `(narrowing)` arm: built the
`@objectstack/dogfood^...` closure, then ran `pnpm --filter
@objectstack/dogfood test` under the lock with no shard and no path.
Result: 344 files passed, 3 skipped (347); 2784 tests passed, 20 skipped
(2804); `VERDICT command-exit 0`.
- `dispatch-gates --commands`: 80 derived families. All 80 exit 0. Three
of them (`check:doc-formula-expressions`, `check:dual-build-cjs-loads`,
`check:lean-entry-closure`) first exited 3 because their prerequisite
was not met. They were run again after the dogfood closure build and
exited 0. `--ran` reconciliation: 80 run, 0 NOT-MEASURED.
- The first head, `17ce2dfb`, had the same readings: 23483 spec tests,
80/80 gates, and the same dogfood counts.
## Acceptance notes
- **The declaration is narrower than the in-process engine, by design.**
`engine.find(obj, { fields: [] })` still reads every column, because the
engine does not parse its options. Everything the new declaration
admits, the engine accepts. Nothing the engine does contradicts a
sentence in this diff. No `.describe()` claims "is refused".
- **The two sibling members first reported here as left loose**
(`DataEngineQueryOptionsSchema.select`,
`DataEngineVectorFindRequestSchema.fields`) are aligned in this PR, at
the seat's request.
- The in-process engine reading `fields: []` as every column is runtime
behaviour outside this card. The seat filed it as #27008.
## Human Minimum
Each item is one rule from
[CONTRIBUTING.md › Human
Minimum](https://github.com/objectstack-ai/objectstack/blob/main/CONTRIBUTING.md#human-minimum),
which links the AGENTS.md text that states it.
- [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic.
- [x] 2. Every name I added uses the casing for its kind.
- [ ] 3. No formatter ran; `pnpm lint` passes. (No formatter ran. `pnpm
lint` is CI's repo-wide run.)
- [x] 4. One issue, one branch off an up-to-date `main`, one pull
request.
- [x] 5. A change that publishes carries a changeset, and both
declaration lines are filled in here and in the changeset.
- [x] 6. `packages/spec` touched: its generated artifacts are
regenerated and committed (`check:generated`: none were stale).
- [x] 7. No hand edits to `content/docs/references/`,
`packages/*/CHANGELOG.md` or `content/docs/releases/`.
- [x] 8. Title, body and comments are in English.
- [x] The *Before Submitting* list in CONTRIBUTING.md is done.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01WGufzfjH9js8WRa8Xi2dmx
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 6845035 commit 49d652e
3 files changed
Lines changed: 163 additions & 35 deletions
File tree
- .changeset
- packages/spec/src/data
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
25 | 25 | | |
26 | 26 | | |
27 | 27 | | |
| 28 | + | |
28 | 29 | | |
29 | 30 | | |
30 | 31 | | |
| |||
1242 | 1243 | | |
1243 | 1244 | | |
1244 | 1245 | | |
| 1246 | + | |
| 1247 | + | |
| 1248 | + | |
| 1249 | + | |
| 1250 | + | |
| 1251 | + | |
| 1252 | + | |
| 1253 | + | |
| 1254 | + | |
| 1255 | + | |
| 1256 | + | |
| 1257 | + | |
| 1258 | + | |
| 1259 | + | |
| 1260 | + | |
| 1261 | + | |
| 1262 | + | |
| 1263 | + | |
| 1264 | + | |
| 1265 | + | |
| 1266 | + | |
| 1267 | + | |
| 1268 | + | |
| 1269 | + | |
| 1270 | + | |
| 1271 | + | |
| 1272 | + | |
| 1273 | + | |
| 1274 | + | |
| 1275 | + | |
| 1276 | + | |
| 1277 | + | |
| 1278 | + | |
| 1279 | + | |
| 1280 | + | |
| 1281 | + | |
| 1282 | + | |
| 1283 | + | |
| 1284 | + | |
| 1285 | + | |
| 1286 | + | |
| 1287 | + | |
| 1288 | + | |
| 1289 | + | |
| 1290 | + | |
| 1291 | + | |
| 1292 | + | |
| 1293 | + | |
| 1294 | + | |
| 1295 | + | |
| 1296 | + | |
| 1297 | + | |
| 1298 | + | |
| 1299 | + | |
| 1300 | + | |
| 1301 | + | |
| 1302 | + | |
| 1303 | + | |
| 1304 | + | |
| 1305 | + | |
| 1306 | + | |
| 1307 | + | |
| 1308 | + | |
| 1309 | + | |
| 1310 | + | |
| 1311 | + | |
| 1312 | + | |
| 1313 | + | |
| 1314 | + | |
| 1315 | + | |
| 1316 | + | |
| 1317 | + | |
| 1318 | + | |
| 1319 | + | |
| 1320 | + | |
| 1321 | + | |
| 1322 | + | |
| 1323 | + | |
| 1324 | + | |
| 1325 | + | |
| 1326 | + | |
| 1327 | + | |
| 1328 | + | |
| 1329 | + | |
| 1330 | + | |
| 1331 | + | |
| 1332 | + | |
| 1333 | + | |
| 1334 | + | |
| 1335 | + | |
| 1336 | + | |
| 1337 | + | |
| 1338 | + | |
| 1339 | + | |
| 1340 | + | |
| 1341 | + | |
| 1342 | + | |
| 1343 | + | |
| 1344 | + | |
| 1345 | + | |
| 1346 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
75 | 75 | | |
76 | 76 | | |
77 | 77 | | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
78 | 113 | | |
79 | 114 | | |
80 | 115 | | |
| |||
101 | 136 | | |
102 | 137 | | |
103 | 138 | | |
104 | | - | |
105 | | - | |
| 139 | + | |
| 140 | + | |
106 | 141 | | |
107 | 142 | | |
108 | 143 | | |
| |||
179 | 214 | | |
180 | 215 | | |
181 | 216 | | |
182 | | - | |
183 | | - | |
| 217 | + | |
| 218 | + | |
184 | 219 | | |
185 | 220 | | |
186 | 221 | | |
| |||
774 | 809 | | |
775 | 810 | | |
776 | 811 | | |
777 | | - | |
778 | | - | |
| 812 | + | |
| 813 | + | |
779 | 814 | | |
780 | 815 | | |
781 | 816 | | |
| |||
907 | 942 | | |
908 | 943 | | |
909 | 944 | | |
910 | | - | |
911 | | - | |
912 | | - | |
913 | | - | |
914 | | - | |
915 | | - | |
916 | | - | |
917 | | - | |
918 | | - | |
919 | | - | |
920 | | - | |
921 | | - | |
922 | | - | |
923 | | - | |
924 | | - | |
925 | | - | |
926 | | - | |
927 | | - | |
928 | | - | |
929 | | - | |
930 | | - | |
931 | | - | |
932 | | - | |
933 | | - | |
934 | | - | |
935 | | - | |
936 | | - | |
937 | 945 | | |
938 | 946 | | |
939 | 947 | | |
| |||
1513 | 1521 | | |
1514 | 1522 | | |
1515 | 1523 | | |
1516 | | - | |
1517 | | - | |
| 1524 | + | |
| 1525 | + | |
1518 | 1526 | | |
1519 | 1527 | | |
1520 | 1528 | | |
| |||
0 commit comments