Repository navigation
feat(spec): GetMetaItemsRequestSchema declares names, the batch by-names read of a /meta/:type list - #29722
Conversation
…mes list read An optional list of item names narrows a /meta/:type list to the named items. An unknown name is simply absent; an empty list is refused rather than read as every item. On the wire it is one comma-separated value (?names=a,b,c), split by the REST door. Claude-Session: https://claude.ai/code/session_01WGufzik1dDv4DzrV1fWBV9 Co-Authored-By: Claude <noreply@anthropic.com>
…rd for names; changeset Claude-Session: https://claude.ai/code/session_01WGufzik1dDv4DzrV1fWBV9 Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGufzik1dDv4DzrV1fWBV9 Co-Authored-By: Claude <noreply@anthropic.com>
📓 Docs Drift Check1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them. What this run could not see
Coarse fallback — 168 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 35e7cae43d2805a73ad8ad8f61e0c249961c3136 && git checkout 35e7cae43d2805a73ad8ad8f61e0c249961c3136
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin c76c0e042ea1efc9ccb7b209a82dd8f0fc119953 bb07931cb445ee6b0d4fc308b359dd87f9766d31 && git checkout -B drift-repro c76c0e042ea1efc9ccb7b209a82dd8f0fc119953 && git merge --no-ff bb07931cb445ee6b0d4fc308b359dd87f9766d31
node scripts/docs-audit/affected-docs.mjs --json c76c0e042ea1efc9ccb7b209a82dd8f0fc119953 |
|
domain:spec#2 · os-support-ai · session_01WGufzhJG3tKnksHVa1krBz Contract reviewServed-tier: Inputs: card #29441 (body, 6582535222, 6592535841, 6592613238, 6594197602), ruling A on #29236 (6580261740), PR #29722 body, file list and the net diff against ① Derived judgmentsEvery accept-set and public-surface change the diff implies, judged against ruling A:
② Semver level
③ Boundary flagsDev report 6594197602 on #29441:
Gate verdicts, read off the head's check-runs at 2026-11-03T03:03Z: 33 success, 8 skipped (the path-filtered UI and opt-in jobs), 0 failure. Every gate-carrying job that had concluded is green — ④ Design judgment
Against ruling A: only the named items are answered ("among those the list would answer without it" keeps the per-caller gate and Tried to break it:
Findings:
No must-fix. Implemented-by: VERDICT: PASS |
…or serves it Claude-Session: https://claude.ai/code/session_01WGufzik1dDv4DzrV1fWBV9 Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGufzik1dDv4DzrV1fWBV9 Co-Authored-By: Claude <noreply@anthropic.com>
|
domain:spec#2 · os-support-ai · session_01WGufzhJG3tKnksHVa1krBz Contract reviewServed-tier: Delta review: two commits on top of the reviewed head ① Derived judgments
② Semver levelUnchanged from the record on ③ Boundary flags
Gate verdicts on ④ Design judgment
Tried to break it: the new pin reads Findings: none. No must-fix, no should-fix. Implemented-by: VERDICT: PASS |
|
domain:spec#2 · os-support-ai · session_01WGufzhJG3tKnksHVa1krBz 席位核对 — ACCEPT · domain:spec#2 · head
|
Requested by Yibo Wang · project thread
Fixes #29441
Clause-②: yes (widening)
Metadata-protocol: added
Skills-docs: none — this PR declares the protocol member only, and the generated reference (
content/docs/references/api/protocol.mdx) teaches it; the wire parameter?names=is not served until the server PR on #29236, which owes theGET /meta/:type?names=teaching (content/docs/api/metadata-api.mdx,skills/objectstack-api/SKILL.md)维护者速读
GetMetaItemsRequestSchema新增可选的names(条目名列表),让/meta/:type列表一次只取指定的几个条目;HTTP 上写作一个逗号分隔值?names=a,b,c。minItems: 1),不会被当成「全部」。服务端未落地前,带names的请求仍按不带处理。回滚即还原本 PR。packages/spec/src/api/protocol.zod.ts(GetMetaItemsRequestSchema的names成员与上方 docblock)、packages/spec/src/api/protocol.test.ts(新 describe)、.changeset/29441-meta-items-names.md。pnpm --filter @objectstack/spec exec vitest run --maxWorkers=2 src/api/protocol.test.ts -t 'batch by-names',应见 7 个用例全绿;把.min(1, …)改成.min(0, …)再跑,空列表与 JSON Schema 两个用例转红。What changes
packages/spec/src/api/protocol.zod.ts,GetMetaItemsRequestSchema(measured at ≈:242 onorigin/main744211fc): a new optional member betweenpackageIdandpreviewDrafts.The describe carries the rule only: only the named items of the type are answered, among those the list would answer without it; over HTTP it is one comma-separated value (
?names=a,b,c); a name that matches no item is simply absent from the answer (no 404); an empty value is refused with 400 rather than read as every item, so leave the key out to list every item. No tracker number is in the describe. The schema's docblock carries the rationale: the ruling, why the member is a list while the wire is one comma string, the push-down to the store read, and why the minimum.The member's type. The request schema is the protocol's post-parse shape, so
namesis a list and the REST door splits the comma string. Measured sibling:GetDataRequestSchema.selectin the same file is the post-parse list of the data route's comma-separatedselectand is typed as a string list (transportProjectionArray(z.string()));GetMetaItemRequestSchema.nameis a barez.string(), so the members arez.string()too (no name grammar: a name outside it is a name that matches no item, which is absent, not refused).The empty-list choice: the schema refuses
[]. The order asked to follow what the?id=sibling does in spec. Measured:?id=has no spec member at all; its empty refusal lives only at the REST door (refuseEmptyListFilter,packages/rest/src/meta-item-read-gate.ts≈:3483), so it answers nothing about a list member. The siblings the ruling names for the spelling (fields,searchFields) do declare it:searchFieldsArray()(packages/spec/src/data/search-fields-names-a-field.ts) isz.array(z.string()).min(1, …), refusing only the empty list and judging no member, andtransportProjectionArrayrefuses[]forfields/select. Both say why: absent means "all", so an empty list read as absent is a widening.namesfollowssearchFieldsArrayexactly:.min(1), no member check. It is a minimum, not a refinement, so the published JSON Schema states it (minItems: 1) anddropped-refinements.baseline.jsondoes not move. The door's400for?names=stays the server PR's (it is in the ruling's server workload).Generated artifacts.
pnpm --filter @objectstack/spec buildthencheck:generatednamed one stale artifact,content/docs/references/**;gen:docsregenerated it (one table row incontent/docs/references/api/protocol.mdx). The build'sgen:schemaaddedapi/GetMetaItemsRequest:namestopackages/spec/authorable-surface/api.json.api-surface/,export-origins/anddeclaration-map/did not move: no export is added, removed or renamed (GetMetaItemsRequestSchemaandGetMetaItemsRequestexist already).check:generatedthen reported all 14 artifacts current.The declaration lines differ from the card's
Metadata-protocol: none. Measured:scripts/check-changeset-no-major.mjsrefusesnonewith anyminorentry (verdictnone-minor) and refusesnonewhenever anauthorable-surface/shard moves (verdictnone-baseline-moved). This diff movesauthorable-surface/api.json, sononecannot pass at any level, and the ruling's "spec minor" needs a kind. The two PRs that last moved that shard with anapikey, #29521 (45b4922d) and #29100 (f1d795b1), declaredClause-②: yes (widening)+Metadata-protocol: added+@objectstack/specminor. This PR declares the same. Seat 2 agreed and amended the claim on #29441. TheClause-②:line keeps the card's(widening)arm.Changeset.
.changeset/29441-meta-items-names.md:@objectstack/specminor, both declaration lines at line start. It says what the member answers, the wire spelling, the empty refusal, and that until a release carries the server change a request carryingnamesis answered as if it were absent.Tests
packages/spec/src/api/protocol.test.tsgainsdescribe('GetMetaItemsRequestSchema declares `names` — the batch by-names list read'), beside the file's other meta-read request blocks, with 7 cases:namesand preserves the list through parse (the parsed value is asserted, since the object is non-strict and a stripped key also readssuccess);namescontrol: a request without it parses and parse invents nonames;[]with onetoo_smallissue at path['names'];['no_such_app'],['']and['crm', 'crm']parse and keep their value;'crm,helpdesk') and a non-string member;z.toJSONSchemastatesnamesas{ type: 'array', minItems: 1, items: { type: 'string' } }and not required;GetMetaItemsRequest['names']isstring[] | undefined(input and output alike, which also keepsIso_api_protocol__GetMetaItemsRequestSchemaintype-alias-convention.pin.test.tstrue).Runs
All under
scripts/pm/os-verify-lock.sh(shared box; wall-clock figures are not idle-box figures).064d4e01d(after theorigin/mainmerge):pnpm --filter @objectstack/spec test:Test Files 803 passed (803),Tests 23793 passed | 1 todo (23794).pnpm --filter @objectstack/spec typecheck: exit 0 (tsc --noEmit,check:scripts-typecheck,check:test-typecheckOK).pnpm --filter @objectstack/spec test:repo:Test Files 89 passed (89),Tests 1522 passed (1522).c968b47f: spectest802/802 files, 23739 tests passed;typecheckexit 0;test:repo89/89 files, 1522 tests passed.Ablation (one, on the tested behaviour)
Committed head
c968b47f, in a separate detached worktree so the gate run was not disturbed. Spec suites import the schema by relativesrcpath, so no build or dist preflight is involved.vitest run src/api/protocol.test.ts -t 'batch by-names':Tests 7 passed | 230 skipped (237).node scripts/ablation-replace.mjs: anchor.min(1, 'Name at least one item, or leave `names` out to list every item')→.min(0, 'ABLATED-29441'); the tool reportsanchor x1 -> x0,replace x0 -> x1, blobe8475ac80907 -> e261fbcb5c77.Tests 2 failed | 5 passed. Failed: "refuses an EMPTY list, atnames" and "states the minimum in the published JSON Schema" (minItems: 0received). Expected direction: red.e8475ac80907= HEAD blob,git diff HEADempty, marker count 0.Acceptance notes
getMetaItemsin@objectstack/metadata-protocol(inline parameter type{ type; packageId?; previewDrafts? },protocol-meta-list-reads.ts:54) does not readnames, and the/meta/:typelist door has no closed query set yet, so?names=is ignored and the full list is answered. The ruling puts the push-down, the door's empty refusal and the closed set in that PR.?names=a,,bis not ruled; this PR neither refuses nor promises it, and the docblock says only that a name matching no item is absent.#29415(seat 2, same file, thecascadeDeleteObjectsdescribes ≈:646/:767/:2480/:2501) touches a different region; the later lander mergesorigin/mainand regenerates.Verification
Local subset, on the final head
064d4e01d(merge oforigin/main3d5933a97); the PR's check-runs carry everything CI runs.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack: 102 derived families (5 changed paths vs merge base3d5933a97), the same 102 as on the pre-merge head. Run serially, each exit code captured before any pipe: 102 exit 0.dispatch-gates --ran: "102 derived, 102 run, 0 NOT-MEASURED, 0 UNRUN".c968b47fthe same 102 ran: 96 exit 0 first pass, 6 exit 3 (PREREQUISITE NOT MET: unbuilt@objectstack/lint/@objectstack/formuladist and the dist-reading gates), all 6 exit 0 after a fullturbo run build(117/117);--ranreconciled 102/102.GITHUB_EVENT_NAME=pull_request node scripts/check-changeset-no-major.mjs --base origin/main --event(this body): exit 0, "LEVEL AXIS: this PR declares clause-②yes (widening)and metadata-protocoladded". Control leg, the same body withMetadata-protocol: none: exit 1,none-baseline-moved("Moved: packages/spec/authorable-surface/api.json").pnpm --filter @objectstack/spec check:generatedafter the build of the merged head: "All 14 generated artifacts are up to date".node scripts/check-closing-keyword-parity.mjs --body(this body): its report mode (exit 2) names one closing declaration,Fixes #29441.pnpm check:dev-prereqs(local-only family): exit 0.check-platform-checklist --changed-fromis not owed.Generated by Claude Code