Skip to content

fix(spec)!: HttpFindQueryParamsSchema declares the count and select values the list door accepts - #26901

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/project-thread-8un53u
Oct 27, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/project-thread-8un53u

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Yibo Wang · project thread

Fixes #26817

Clause-②: no (narrowing)
Metadata-protocol: none
Skills-docs: none — skills/** teaches only the boolean count (skills/objectui/guides/data-integration.md:90, $count: true) and never a blank select; the generated reference page content/docs/references/api/protocol.mdx is regenerated in this diff.

BREAKING (accept-set narrowing of a published declaration, patch): HttpFindQueryParamsSchema refuses a count other than true/false and a select naming no field, which it admitted. The list door already refused both with a 400 (since #26811). ADR-0087 disposition in the changeset: not-required (no-migration-prescription).

维护者速读

  • 改了什么:GET /data/:object 查询参数的公开声明 HttpFindQueryParamsSchema 收窄:count 只收 true/false(字符串 'false' 读作 false),select 必须至少点名一个字段,空串、纯空格、只有逗号的值都被拒。
  • 为什么改:服务端列表接口早已对这些值回 400,但声明还写着「什么都收」,'false' 甚至被读成 true;按声明生成的客户端或 AI 作者会写出运行时被拒的请求。
  • 风险与回滚:只影响用这个 schema 解析查询串的代码;接口运行时行为不变(声明没有运行时读者)。回滚只需还原 packages/spec/src/api/protocol.zod.ts 里该块的两行声明。
  • 先看:packages/spec/src/api/protocol.zod.ts(HttpFindQueryParamsSchema 的 select、count 两行)、packages/spec/src/api/protocol.test.ts(新增用例)、.changeset/26817-http-find-query-params-count-select.md。
  • 两分钟验证:pnpm --filter @objectstack/spec exec vitest run src/api/protocol.test.ts,预期 215 个用例全绿,其中 reads count="false" as false、refuses count="maybe"、refuses select="," 都在。
  • 需要你做的:无,席位落地。

What changes

  • count reads the door's accept set from the one declaration the door parses, QueryTransportParamsSchema.shape.count (which findData's countFlagValue checks): the boolean, or the strings 'true' / 'false'. The nullable arm is unwrapped (a querystring carries no null), and the value is lowered to the boolean it means, so 'false' parses as false. It replaces z.coerce.boolean(), which read 'false' as true and admitted 'maybe', '1' and a blank value.
  • select must name at least one field: z.string().regex(/[^\s,]/). This is the door's own rule measured in projectionFieldsValue: it splits on commas, trims each member, drops empty members and refuses only when nothing is left. So a blank, whitespace-only or comma-only value is refused, and name,,email or name , email stay accepted, as at the door. \s is the set trim() strips. A regex is a string format check, not a .refine(), so it is published as a JSON Schema pattern and adds no dropped-refinements row.
  • .describe() texts state the new accept sets; the reference page regenerates from them.

Tests

  • packages/spec/src/api/protocol.test.ts: count 'true'/'false'/true/false parse to true/false; 'maybe', '1', '0', '', 'TRUE', 1, null refused; select '', ' ', ',', ' , ,', tab refused; name, name,email, name , email, name,,email accepted unchanged.

Acceptance notes

  • Readers of HttpFindQueryParamsSchema. Regenerated: content/docs/references/api/protocol.mdx (gen:docs, in this diff). Unchanged by construction (key-level, keys unchanged): authorable-surface, api-surface, declaration-map, export-origins, json-schema.manifest, dropped-refinements.baseline.json; check:generated and the build are green. Prose only: packages/client/src/index.ts:371, packages/client/src/client.test.ts:1969, packages/runtime/src/domains/data.ts:410, packages/spec/src/data/query.zod.ts:215, ui/packages/app-shell/src/console/organizations/manage/orgEnvironments.ts:64, CHANGELOG entries. Test reader: packages/metadata-protocol/src/protocol.page-window-declared.test.ts (top/skip only), green.
  • Clients in this repo. No client sends count at all, and none sends a non-boolean count string (git grep over packages, examples, apps). ObjectStackClient.data.find and ScopedEnvironmentClient.data.find (packages/client/src/index.ts:7060, :7946) send select as select.join(',') whenever select is truthy, so select: [] sends ?select=. Not changed here: the door answers it with a 400 naming select, and dropping it client-side would turn an empty projection into "every column", the widening fix(metadata-protocol)!: the Data API refuses an empty projection and a count flag that is neither true nor false #26811 closed. Reported to the seat for a ruling.
  • Siblings, out of scope. GetDataRequestSchema.select (z.array(z.string())) is on the by-id door (getData), not findData, and admits [] that getData refuses. QueryTransportParamsSchema.select / $select (and the canonical fields) sit on the same findData door and admit a blank string or [], but they also feed QueryWithTransportSchema and the POST /data/:object/query body declaration, outside this card's file surface. Both are reported to the seat, not folded.
  • Clause-② and Metadata-protocol. no (narrowing): only a published accept set shrinks, nothing is widened, no key moves. Metadata-protocol: none: this is a querystring transport declaration, not metadata an author stores; no metadata type, key or stored shape changes.

Verification

Final head: c3c1f10ae (merge of origin/main 5787df4).

Spec tests and typecheck

pnpm --filter @objectstack/spec test then typecheck (lock VERDICT command-exit 0 for both; the chain's third leg needed a full build): Test Files 735 passed (735), Tests 22061 passed, 1 todo. After the full build: metadata-protocol protocol.page-window-declared.test.ts + protocol.empty-projection-unknown-count.test.ts: 2 files, 532 tests passed. Full pnpm build: Tasks 116 successful, 116 total.

Derived gates (103) at c3c1f10

node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack: 103 families, each run with the exit code captured before any pipe; all 103 exit 0 after the full build. --ran: "103 derived famil(ies) accounted for — 103 run, 0 NOT-MEASURED". Local-only: check:dev-prereqs exit 0, check:skill-frame-freshness exit 0. Before the build, on 794bcd8, 97 exit 0 and 6 exit 3 (unbuilt dist).

Full dogfood at c3c1f10

pnpm --filter @objectstack/dogfood test under the lock: Test Files 1 failed, 326 passed, 3 skipped (330); Tests 2 failed, 2681 passed, 20 skipped (2703). Both failures are packaged-action-disabled-dispatch-live.dogfood.test.ts (acceptance[0] and acceptance[5]/[2]), a file added by #26829 at 18:17Z today. It reads no count or select, and HttpFindQueryParamsSchema has no runtime reader. The same file re-run alone on the same head and dist: 1 file, 8 tests passed. Read as load-dependent flake, not this diff.


🤖 Generated with Claude Code

https://claude.ai/code/session_01WGufzh5dnFHGxFUBugn12b

…alues the list door accepts

count reads the door's accept set from QueryTransportParamsSchema.shape.count
and parses 'false' as false; select must name at least one field.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGufzh5dnFHGxFUBugn12b
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

1 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
  • the SDK route bridge reached 55 of 207 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 147 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json a51ba639c165861a8c4ac51015e554911960bf24 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from f0c8193ab35427261ae9d04b15418382ac87f445 — the merge of head c3c1f10aea1b96bdcd353a861bbcb74b7fa37d02 into base a51ba639c165861a8c4ac51015e554911960bf24, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin f0c8193ab35427261ae9d04b15418382ac87f445 && git checkout f0c8193ab35427261ae9d04b15418382ac87f445
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin a51ba639c165861a8c4ac51015e554911960bf24 c3c1f10aea1b96bdcd353a861bbcb74b7fa37d02 && git checkout -B drift-repro a51ba639c165861a8c4ac51015e554911960bf24 && git merge --no-ff c3c1f10aea1b96bdcd353a861bbcb74b7fa37d02

node scripts/docs-audit/affected-docs.mjs --json a51ba639c165861a8c4ac51015e554911960bf24

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling labels Oct 27, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: c3c1f10aea1b96bdcd353a861bbcb74b7fa37d02
Local-runs: none
Scope: packages/spec/src/api/protocol.zod.ts, packages/spec/src/api/protocol.test.ts, .changeset/26817-http-find-query-params-count-select.md, content/docs/references/api/protocol.mdx + packages/spec/src/data/data-engine.zod.ts (TransportCountValueSchema, QueryTransportParamsSchema.count/select/$select, QueryPageWindowParamSchema), packages/metadata-protocol/src/protocol.ts (projectionFieldsValue :4645, countFlagValue :4672, their call sites :12996 / :13176 / :13576), packages/client/src/index.ts (:371, :7061, :7947), packages/runtime/src/domains/data.ts (:408-416), packages/metadata-protocol/src/protocol.page-window-declared.test.ts, packages/spec/dropped-refinements.baseline.json + packages/spec/scripts/lib/{dropped-refinements,refinement-projection}.ts, packages/spec/{authorable-surface,declaration-map,export-origins,json-schema.manifest}/api.json, packages/spec/api-surface/api.json, packages/spec/docs-import-surface.baseline.json, scripts/check-adr-0087-registration.mjs, scripts/pm/clause2-line.mjs, skills/objectui/guides/data-integration.md, packages/qa/dogfood/test/packaged-action-disabled-dispatch-live.dogfood.test.ts, PR #26811 diff (the door)

Inputs read: card #26817 body + comments 6449665015 (triage), 6450457073 (claim), 6452436506 (os-dev-report); PR #26811 diff; PR #26901 body, file list (4 paths), the one comment 6452440544 (docs-drift bot), net diff at c3c1f10ae; check-runs on c3c1f10ae read at 2026-10-27T20:14Z (39 runs: 13 success, 5 skipped, 21 in_progress), 2026-10-27T20:19Z, 2026-10-27T20:20Z (45 runs: 25 success, 7 skipped, 13 in_progress) and finally as stamped in ③. Head unchanged from the brief; /pulls/26901 head.sha re-read at the final reading.

① Derived judgments

  1. count now admits exactly the door's accept set, from one source. The door (countFlagValue, protocol.ts :4672, landed by fix(metadata-protocol)!: the Data API refuses an empty projection and a count flag that is neither true nor false #26811) parses the value with QueryTransportParamsSchema.shape.count.safeParse — TransportCountValueSchema = z.union([z.boolean(), z.enum(['true','false'])]) (data-engine.zod.ts :908) wrapped .nullable().optional() — and lowers a string to data === 'true'. The PR reads the same node (QueryTransportParamsSchema.shape.count.unwrap().unwrap()) and lowers identically (flag === true || flag === 'true'). Not a copy: a change to TransportCountValueSchema reaches both ends; a change to the nullable/optional wrapping order would throw at first access of the lazy schema, i.e. loudly, not silently drift. The only difference is null: the door treats null as absent (options.count != null guard, :13176), the declaration refuses it — correct for its medium, a querystring carries no null; pinned (null in the refused list). 'false'→false, 'true'→true, true/false pass-through, 'maybe'/'1'/'0'/''/'TRUE'/1 refused: all pinned in protocol.test.ts (new it.each blocks at :695-719). The enum is case-exact at both ends, so 'TRUE' is refused at both. Output type z.infer stays boolean | undefined; the docs-import-surface baseline records "api/HttpFindQueryParams — no type export", so api-surface is unmoved by construction.
  2. select now admits exactly the door's accept set, by an equivalent rule. The door (projectionFieldsValue, protocol.ts :4645) does split(',').map(trim).filter(Boolean) and refuses iff nothing survives. The declaration's /[^\s,]/ accepts iff some character is neither whitespace nor a comma ⇔ some comma-segment has a non-whitespace character ⇔ trim(segment) !== '' ⇔ the door's list is non-empty. JS String.prototype.trim strips exactly WhiteSpace ∪ LineTerminator, which is exactly the regex \s class, so the two sets coincide on every code point (NBSP, U+3000, BOM included). Whitespace-only, comma-only, ' , ,', '\t': refused at both ends and pinned; 'name,,email' and ' name , email ': accepted at both ends and pinned, returned unchanged (the declaration does not normalise — the door does the split; correct, a declaration of the wire string). This is a string-format check (string_format), not a custom check, so by refinement-projection.ts :65-69 and dropped-refinements.ts :234-238 it projects as JSON Schema pattern and adds no dropped-refinements site; the existing api/HttpFindQueryParams entry (top.options[1].in) stays as is. The .transform on count is a pipe with no custom check: no site either. The dev's "unchanged by construction" claim for the six generated ledgers holds for the right reason (key-level inventories; the one value-level ledger counts only custom checks).
  3. Readers. Code readers of HttpFindQueryParamsSchema on main: none at runtime — runtime/src/domains/data.ts :408-416 says the GET params "ride through VERBATIM" and the protocol normaliser parses QueryTransportParamsSchema, not this schema; the only code reader is protocol.page-window-declared.test.ts (top/skip through viaHttpParams, unaffected). Generated readers: content/docs/references/api/protocol.mdx regenerated in the diff; both new .describe() texts are true to the accept sets ("at least one field name; a value naming no field (blank, or only commas and spaces) is refused" / "true or false; any other value is refused") and the rendered type boolean \| Enum<'true' \| 'false'> is the input side, which is what a client reads. authorable-surface, declaration-map, export-origins, json-schema.manifest, api-surface: key/export-level, no key moved. Prose readers (client :371 on expand, client.test :1969, query.zod :215, ui app-shell :64): about other keys or the schema's existence, none contradicted. skills/**: git grep for select=, count=, $count=, HttpFindQueryParams, coerce.boolean finds nothing; data-integration.md:90 teaches $count: true only. Skills-docs: none — REASON is the SKILL.md-permitted spelling and the reason is true.
  4. Dev ruling request (1) — client select: []. ObjectStackClient.data.find (:7061) and ScopedEnvironmentClient.data.find (:7947) run if (normalizedOptions.select) queryParams.set('select', select.join(',')), so select: [] sends ?select=, which the door has refused with 400 VALIDATION_ERROR naming select since fix(metadata-protocol)!: the Data API refuses an empty projection and a count flag that is neither true nor false #26811. This PR makes the published declaration say the same, so the client is now visibly sending a request the contract refuses — but the mismatch is client-vs-door and pre-dates this PR; the PR only stops the declaration from hiding it. Leaving the client untouched here is right: it is outside the claimed file surface, and the two client-side fixes have different costs. Ruling: ⛔ not "drop the param when empty" (that is the every-column widening fix(metadata-protocol)!: the Data API refuses an empty projection and a count flag that is neither true nor false #26811 closed, and a lenient consumer accommodation, Prime Directive 12). The correct client fix is a client-lane card: refuse select: [] before the round trip with the door's own sentence (or type select as a non-empty array), so the SDK fails at the call site rather than after a network hop. Not a FAIL reason for this PR.
  5. Dev out-of-scope findings (2). (a) GetDataRequestSchema.select: z.array(z.string()) (protocol.zod.ts :2407) admits [], which the by-id door getData refuses (:13576, projectionFieldsValue(request.select, 'select')). Different door (by-id), same defect family: a separate card, correctly. (b) QueryTransportParamsSchema.select (z.union([z.string(), z.array(z.string())])) and $select admit ''/[] that the SAME findData door refuses (options.fields → projectionFieldsValue, :12996). Same door, same family ("declaration wider than the findData door") — but a different declaration in a different file (data-engine.zod.ts) with far more readers: QueryWithTransportSchema, FindDataRequestSchema.query, the POST /data/:object/query body, the RPC alias table and QuerySchema.fields; the card named the GET querystring declaration and the claim's file surface stopped at that block. Folding it in would breach the claimed surface and change the body-side declaration without its own premise reading — a separate card is the right call, and it must be filed, not merely "reported to the seat" (Prime Directive 10). The count reuse does not inherit or hide the blank-select gap: only .shape.count is reused; select is declared independently with its own regex. What the reuse does create is two declarations of one door that now disagree on select (HttpFindQueryParams.select strict, QueryTransportParams.select loose) — the very two-declaration condition data-engine.zod.ts :990-1010 warns about for $orderby; the sibling card closes it.
  6. Dogfood. packaged-action-disabled-dispatch-live.dogfood.test.ts (packages/qa/dogfood/test, from test(dogfood): pin five more api-backend checklist items at their oracle's layer #26829) boots the showcase, calls MCP tools/call run_action and does GET /data/showcase_task?$top=1 and GET /data/showcase_task/:id — no count, no select, and HttpFindQueryParamsSchema has no runtime reader, so nothing in this diff can change what that test observes; the new import edge (protocol.zod.ts → data-engine.zod.ts) already existed for QueryWithTransportSchema and data-engine.zod.ts imports nothing from api/, so no new cycle. The flake claim (2 failed under the full battery, 8/8 alone on the same head and dist) is supported by the evidence and by the file's 240 s boot timeout being load-sensitive. Note for the record: the CI Dogfood Regression Gate (N/3) shards are path-filtered off on this PR (skipped), so CI confirms nothing about that file either way; the judgment rests on reach, not on a green run.
  7. Declarations. Clause-②: no (narrowing) is right — nothing widens, no key moves, a published accept set shrinks (triage 6449665015 wrote yes (narrowing); the claim and the dev corrected it to no, which clause2-line.mjs :111-117 defines as "not a widening, but breaking"; the changeset's no (narrowing) — prose spelling reads the arm as the first token inside the parens, :269-272). Metadata-protocol: none is right: a GET /data/:object querystring is a transport request, not metadata an author writes or stores; no metadata type, key, default or stored shape moves (the claim's provisional narrowed was flagged for re-judging and the dev's none is the correct re-judgment). ADR-0087: not-required (no-migration-prescription) — there are no stored rows and nothing for a conversion-layer entry to rewrite; the changeset's "The remedy" sentence is caller advice with no FROM→TO, framing heading or table, so the gate's statement-against-statement check does not refuse it, and Lint · CI, release & engine gates is green. **BREAKING banner present; both declaration lines line-initial in the PR body and the changeset.
  8. Scope of the diff. Four paths, all on the card: the schema block, its test, the regenerated reference page, one changeset. The import-line edit outside the block is the one the card's own "Wanted" (reuse QueryTransportParamsSchema.shape.count) requires. No unrelated file.

② Semver level

@objectstack/spec patch — correct. The diff moves one published package; Metadata-protocol: none takes patch (AGENTS.md Post-Task Checklist 3: "a non-protocol change, widening or breaking, ships patch and keeps its BREAKING banner and ADR-0087 disposition"); the banner and the one marker are in the changeset body. content/docs/** is not a package. Check Changeset green.

③ Boundary flags

  • Dev deviation "PR assignee none": the card has no assignee; accepted.
  • Dev deviation "client select: [] not changed": accepted for this PR, ruled in ① 4 — escalated to the seat to file a client-lane card (refuse an empty projection at the SDK call site; ⛔ never drop the parameter).
  • Dev deviation "import line outside the block": accepted — required by the reuse the card asked for.
  • Out-of-scope findings (a) GetDataRequestSchema.select [] and (b) QueryTransportParamsSchema.select/$select blank/[]: both judged rightly separate in ① 5; escalated to the seat to FILE both cards (Prime Directive 10) — (b) is the same findData door and closes the two-declaration disagreement this PR leaves on select.
  • Triage Clause-②: yes (narrowing) vs PR no (narrowing): PR is right; no action.
  • Dogfood 2 failures: flake claim supported (① 6); no action on this PR.
  • Gates: see the reading below. Every required check-run on c3c1f10ae concluded success or skipped (skipped = path-filtered or opt-in); none failure/cancelled/action_required.

Gate reading of record, 2026-10-27T20:34Z: 47 check-runs on c3c1f10ae, 40 completed/success, 7 completed/skipped (Lint UI, Test UI (N/16), Dogfood Verify CLI, Dogfood Regression Gate (N/3), Temporal Conformance, Packed-tarball smoke (opt-in), 18.x upgrade harness (opt-in) — path filters and opt-ins), 0 in_progress, 0 failure. Green and load-bearing for this diff: TypeScript Type Check (the six spec artifact gates incl. check:docs), Build Core, Test Core 8/8, Check Changeset, Lint · CI, release & engine gates (ADR-0087 marker), Governed Surface Queue Guard, Spec property liveness, Build Docs.

Written 2026-10-27T20:35Z.

Implemented-by: claude/project-thread-8un53u
Reviewed-by: session_01WGufzgbJhFoXYsLSe5WSPP

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

维护者速读 (席位终稿)

  • 改了什么:GET /data/:object 查询参数的公开声明 HttpFindQueryParamsSchema 收窄到接口真正接受的范围:count 只收 true / false(字符串 'false' 读作 false),select 必须至少点名一个字段,空串、纯空格、只有逗号的值都会被拒。
  • 为什么改:服务端列表接口自 fix(metadata-protocol)!: the Data API refuses an empty projection and a count flag that is neither true nor false #26811 起就对这些值回 400,但声明还写着「什么都收」,'false' 甚至被读成 true。按声明生成的客户端或 AI 作者会写出运行时被拒的请求。
  • 风险与回滚:只影响用这个 schema 解析查询串的代码;接口运行时行为不变(这份声明没有运行时读者)。回滚 = revert 本 PR。
  • 先看:packages/spec/src/api/protocol.zod.ts(HttpFindQueryParamsSchema 的 select、count 两行)、packages/spec/src/api/protocol.test.ts(新增用例)。
  • 两分钟验证:pnpm --filter @objectstack/spec exec vitest run src/api/protocol.test.ts,预期全绿,其中 reads count="false" as false、refuses count="maybe"、refuses select="," 都在。
  • 需要你做的:无,席位落地。

席位意见:接受。契约复核 PASS(6452937369)。同一规则的两个兄弟声明(请求体侧的 select、按 id 读取的 select)还收空投影,已立卡 #26909;客户端 select: [] 会发出被拒的请求,已立卡 #26910。

席位核对 · ACCEPT · domain:spec seat 3 · os-support-ai · session session_01WGufzgbJhFoXYsLSe5WSPP · 2026-10-27T20:38Z

Head: c3c1f10aea1b96bdcd353a861bbcb74b7fa37d02 · card #26817 · claim 6450457073

  1. 形态: a draft PR to main with Fixes #26817 as its only closing keyword. 4 paths, +65 / −5: the schema block in protocol.zod.ts, its tests, the regenerated protocol.mdx reference page and one changeset. All are inside the claim surface; the one import line outside the block is the reuse the card asked for. check-governed-merges --pr 26901: NOT governed, 70 lines ≤ 3000, so the ordinary queue landing applies. The PR carried no assignee; the seat sets os-support-ai in this act.
  2. 前提实测: the premise holds on 4b59358a (claim) and at the head's base: count was z.coerce.boolean() and select was z.string(). The review read the door PR fix(metadata-protocol)!: the Data API refuses an empty projection and a count flag that is neither true nor false #26811 landed: count reuses the exact node the door parses (QueryTransportParamsSchema.shape.count, lowered the same way), and the select regex /[^\s,]/ is equivalent to the door's split / trim / drop-empty rule on every input (JS \s is the set trim() strips). The only difference is null, which a querystring cannot carry (record ① 1–2).
  3. 门禁 check-runs (on this head): 47 runs, 40 success, 7 skipped (path filters and opt-ins), none red or pending; mergeable state clean. The contract-tier review is PASS on this same head (record 6452937369, narrowing face plus the schema face's lines), and the head has not moved since. Dogfood: the dev's full battery had 2 failures in packaged-action-disabled-dispatch-live (from test(dogfood): pin five more api-backend checklist items at their oracle's layer #26829), 8/8 green alone on the same head; the review found no path from this diff to that test (the schema has no runtime reader and the test sends no count or select), so the seat accepts the flake reading. The CI dogfood shards were path-skipped on this PR.
  4. 接受的偏差: (a) Clause-②: no (narrowing), the seat rule for narrowing a published accept set; triage wrote yes (narrowing). (b) Metadata-protocol: none, re-judged by the dev from the claim's provisional narrowed: a GET querystring is a transport request, not stored or authored metadata. The review upheld it, which makes @objectstack/spec a patch with its BREAKING banner. (c) ADR-0087: not-required (no-migration-prescription): nothing is stored.
  5. 范围外去向: filed in this act. spec: QueryTransportParamsSchema.select / $select and GetDataRequestSchema.select admit an empty projection that the findData and getData doors refuse (sibling of #26817) #26909: QueryTransportParamsSchema.select / $select (the same findData door, body side) and GetDataRequestSchema.select (the by-id door) still admit an empty projection; after this PR the two declarations of findData disagree on select until that card lands. client: data.find sends ?select= for select: [] — refuse an empty projection at the call site, never drop the parameter (follow-up of #26817) #26910: the client's data.find sends ?select= for select: []; it should refuse at the call site and ⛔ never drop the parameter (that would widen to every column). Skills-docs: none is upheld: no skill teaches these query parameters.
  6. 落地条款: the prose faces were checked against the diff: the changeset's BREAKING text and both .describe() texts, which state the new accept sets. Ready, auto-merge and the assignee go through the relay in this act, and the seat follows the PR to MERGED.

Generated by Claude Code

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 tests tooling

Projects

None yet

2 participants