Repository navigation
fix(spec)!: transport select / $select and GetDataRequestSchema.select declare the projection the doors accept - #26949
Conversation
…t declare the projection the doors accept QueryTransportParamsSchema.select / $select admitted a blank or comma-only string, [] and blank names, and GetDataRequestSchema.select admitted [], all of which the findData / getData doors refuse. One rule in data-engine.zod.ts now declares the door's accept set; HttpFindQueryParams and GetDataRequest read its arms instead of restating them. The POST /data/:object/query body gate parses FindDataRequestSchema, so an empty projection there is now refused at the schema (400 VALIDATION_FAILED naming query.select), as a malformed limit or count already is; the rest and runtime door pins move with it. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WGufzgm45oXexLiY5mMigQ
…ndex Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WGufzgm45oXexLiY5mMigQ
📓 Docs Drift Check7 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run. What this run could not see
Coarse fallback — 147 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 7b6db5baa8f354715154f4f9dd9746b5f08a95f3 && git checkout 7b6db5baa8f354715154f4f9dd9746b5f08a95f3
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin aa00362fe5f567f15c71cec8f78c85263ba1eeb6 18214431cb8e44001c0b415b2ff3afa18dcb0271 && git checkout -B drift-repro aa00362fe5f567f15c71cec8f78c85263ba1eeb6 && git merge --no-ff 18214431cb8e44001c0b415b2ff3afa18dcb0271
node scripts/docs-audit/affected-docs.mjs --json aa00362fe5f567f15c71cec8f78c85263ba1eeb6 |
Contract reviewServed-tier: Inputs read: card #26909 body + comments 6453677540 (triage), 6454397712 (claim), 6454444680 (claim amendment, two rulings), 6455440538 (os-dev-report); predecessor card #26817, PR #26901 (merged ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS |
维护者速读 (席位终稿)
席位意见:接受。契约复核 PASS(6455655029)。复核发现另外两份引擎侧声明还收空数组,已立卡 #26954。 席位核对 · ACCEPT ·
|
Requested by Yibo Wang · project thread
Fixes #26909
Clause-②: no (narrowing)
Metadata-protocol: none
Skills-docs: none —
skills/**teaches only a named projection (skills/objectui/guides/data-integration.md:82,$select: ['name', 'email']) and never an empty one; the generated reference pagescontent/docs/references/{api/protocol,data/data-engine}.mdxare regenerated in this diff.ADR-0087: not-required (no-migration-prescription) — in the changeset.
BREAKING (accept-set narrowing of published transport declarations,
patch):QueryTransportParamsSchema.select/$select(soQueryWithTransportSchemaandFindDataRequestSchema.query) andGetDataRequestSchema.selectrefuse a projection naming no field, which they admitted. The doors already refused it with a 400.维护者速读
select/$select) 的三处公开声明收窄到接口真正接受的范围:空串、纯空格、只有逗号、空数组、含空白字段名的数组都被拒;三处声明共用同一条规则。POST /data/:object/query:空投影仍是 400 且不读数据,但错误码从VALIDATION_ERROR变成请求体 schema 的VALIDATION_FAILED(与该接口对limit、count的既有做法一致,先例 spec: the published query schemas declare limit / offset / top / skip as plain numbers the Data API now refuses (declared ≠ enforced) #25269)。回滚 = revert 本 PR。packages/spec/src/data/data-engine.zod.ts(新增的投影规则与select/$select两行)、packages/spec/src/api/protocol.zod.ts(HttpFindQueryParamsSchema.select与GetDataRequestSchema.select改为引用同一规则)、.changeset/26909-transport-select-empty-projection.md。pnpm --filter @objectstack/spec exec vitest run src/data/query-transport.test.ts src/api/protocol.test.ts,预期全绿,其中§6 the projection spellings admit exactly what the doors accept一组都在。What changes
data-engine.zod.tsgains the projection's accept set besideQueryTransportParamsSchema: a string armz.string().regex(/[^\s,]/)(byte-identical to the rule spec: HttpFindQueryParamsSchema declares count as z.coerce.boolean and select as any string, wider than the list door (which refuses 'false'-as-string oddities, unknown counts and a blank select) #26817 gaveHttpFindQueryParamsSchema.select, which the fix(spec)!: HttpFindQueryParamsSchema declares the count and select values the list door accepts #26901 review proved equal to the door's split / trim / drop-empty) and an array armz.array(member.regex(/\S/)).min(1).selectand$selectuse both arms ($selectkeepsFieldNodeSchemaas its member, so the retired object-form message is unchanged).HttpFindQueryParamsSchema.selectnow reads the string arm offQueryTransportParamsSchema.shape.selectandGetDataRequestSchema.selectreads the array arm, as fix(spec)!: HttpFindQueryParamsSchema declares the count and select values the list door accepts #26901 already readcount: no new export, no copy. Both are patterns and a length check, not refinements, so they project into JSON Schema (pattern,minItems) and add nodropped-refinementssite.projectionFieldsValue(metadata-protocol/src/protocol.ts:4648) keeps every array member and refuses only[];assertProjectionFieldsExistthen checks every member against the object's fields, where a blank name is never one (400 INVALID_FIELD). The declaration refuses[]and any blank member, which covers the all-blank list the card names and['name', ''], both refused by the door today..describe()texts of the three members now state the accept set; the two reference pages are regenerated.HttpFindQueryParamsSchema.select's row is unchanged.POST /data/:object/queryparses its body withFindDataRequestSchema(rest/src/crud-routes.ts:363,runtime/src/domains/data.ts:242), so an emptyselect/$selectis now refused at the schema:400 VALIDATION_FAILED,fields[0].field=query.select/query.$select, still before any read. This follows the spec: the published query schemas declare limit / offset / top / skip as plain numbers the Data API now refuses (declared ≠ enforced) #25269 precedent (rest/src/data-page-window-door.test.tsheader: "the code is the schema door's rather than the normalizer's, because the schema now says what the normalizer enforces"), the same way that route already answers{"count":"maybe"}. The two POST rows inrest/src/data-empty-projection-count-door.test.tsandruntime/src/domains/data-empty-projection-count.test.tsmove to newVALIDATION_FAILEDtests, which also cover[]and['name', ''].{"fields": []}(canonicalQuerySchema.fields, not in this card) and every GET row stayVALIDATION_ERROR, unchanged.Why
After #26901, two declarations of the one
findDatadoor disagreed onselect(the GET querystring strict, the body side loose), and the by-id request admitted[]thatgetDatarefuses. A generated client, a docs reader or an AI author trusting the published schema learned the rule only from a runtime 400.Readers measured
QueryWithTransportSchema/FindDataRequestSchema.query: thePOST /data/:object/querybody gate in rest and the runtime dispatcher. The one runtime behaviour change, above.foldRpcQueryOptions,QuerySchema.fields: untouched;QuerySchemadoes not read these members.GetDataRequestSchema: no runtime parse (the by-id routes handselecttogetData, which refuses throughprojectionFieldsValue); published declaration only.HttpFindQueryParamsSchema: no runtime reader; same regex and message, so fix(spec)!: HttpFindQueryParamsSchema declares the count and select values the list door accepts #26901's pins stay green unchanged.content/docs/references/**regenerated;check:generatedgreen on 14 artifacts (key-level ledgers unmoved).data.findsending?select=forselect: []is client: data.find sends ?select= for select: [] — refuse an empty projection at the call site, never drop the parameter (follow-up of #26817) #26910 (client lane), not touched.QueryWithTransportSchema,QueryTransportParamsSchemaorGetDataRequestSchema(onlyFindDataRequestSchema.query), soMetadata-protocol: none.Tests
Locally, under
os-verify-lock:@objectstack/spectest (96f9915; the later commit touches only rest/runtime tests): Test Files 738 passed, Tests 22136 passed, 1 todo.typecheck: exit 0.@objectstack/metadata-protocoltest (96f9915): Test Files 273 passed, 4 skipped; Tests 21693 passed, 26 skipped. Unchanged, includingprotocol.empty-projection-unknown-count.test.ts(in-processfindData, no schema).@objectstack/rest(1821443):data-empty-projection-count-door,data-page-window-door,zod-union-fields,rest-server-canonical-query-ast: 4 files, 127 passed, 18 skipped.@objectstack/runtime(1821443):data-empty-projection-count,data-write-request-gate,data-record-read-status-parity: 3 files, 96 passed.query-transport.test.ts§6 ('',' ',' , ',',',[],['', ' '],['name', '']refused onselectand `` byQueryTransportParamsSchemaand `QueryWithTransportSchema`, the issue at the member's own path; string and array lists kept unchanged; `null` still a withdrawal); `protocol.test.ts` (`GetDataRequestSchema.select` refuses `[]`, `['', ' ']`, `['name', '']` and keeps lists; `FindDataRequestSchema.query` refuses the blank forms on both spellings). fix(spec)!: HttpFindQueryParamsSchema declares the count and select values the list door accepts #26901's `HttpFindQueryParamsSchema` pins are untouched and green.query.select.1), notquery.select; the pins name the index (1821443).pnpm --filter @objectstack/dogfood test, no shard, lock VERDICT command-exit 0): Test Files 340 passed, 3 skipped; Tests 2754 passed, 20 skipped; no failures. 1821443 changes only two rest/runtime test files, which dogfood does not import.Acceptance notes
VALIDATION_ERROR; that cannot hold for the POST body without changing the rest and runtime gates, and seat 3 accepted the spec: the published query schemas declare limit / offset / top / skip as plain numbers the Data API now refuses (declared ≠ enforced) #25269 route (claim amendment) before the pins moved.Verification
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackon 1821443: 105 derived families, run serially with no build in flight, each exit captured before any pipe: 105 exit 0.--ranreconciliation: "105 derived famil(ies) accounted for — 105 run, 0 NOT-MEASURED". (The same list on 96f9915 had two built-output gates,check:skill-examplesandcheck:dual-build-cjs-loads, exit 3 before the dogfood closure build; both exit 0 on the rerun.)pnpm --filter @objectstack/spec check:generated --fix: 14 artifacts, onlycontent/docs/references/**regenerated. CI families: read from this PR's check-runs.🤖 Generated with Claude Code
https://claude.ai/code/session_01WGufzgm45oXexLiY5mMigQ