Repository navigation
Commit 6f09464
feat(spec): GetMetaItemsRequestSchema declares names, the batch by-names read of a /meta/:type list (#29722)
<!-- claude-projects-attribution: {"github_login":"os-support-ai"} -->
_Requested by **Yibo Wang** · [project
thread](https://claude.ai/code/project/chan_01BVUyyPie8gHwoPM18iABwB?thread=cmsg_01BVUyyPie8gHwoPM18iABwBA4TcdtpTxT1Yyhx9c83Z5C)_
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 the `GET /meta/:type?names=` teaching
(`content/docs/api/metadata-api.mdx`, `skills/objectstack-api/SKILL.md`)
## 维护者速读
- 改了什么:`GetMetaItemsRequestSchema` 新增可选的 `names`(条目名列表),让 `/meta/:type`
列表一次只取指定的几个条目;HTTP 上写作一个逗号分隔值 `?names=a,b,c`。
- 为什么改:执行 #29236 裁决 A(6580261740,维护者「同意」),裁决要求先落 spec 这一半;服务端 PR
被本卡阻塞。调用方今天要么读整类、要么逐个按名读。
- 风险与回滚:只加一个可选成员,原来能解析的请求照样解析;空列表在 schema 层就拒收(JSON Schema 的 `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 on `origin/main` `744211fc`): a new optional member
between `packageId` and `previewDrafts`.
```ts
names: z.array(z.string())
.min(1, 'Name at least one item, or leave `names` out to list every item')
.optional()
.describe(…)
```
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 `names` is a list and the REST door splits the comma string.
Measured sibling: `GetDataRequestSchema.select` in the same file is the
post-parse list of the data route's comma-separated `select` and is
typed as a string list (`transportProjectionArray(z.string())`);
`GetMetaItemRequestSchema.name` is a bare `z.string()`, so the members
are `z.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`) is
`z.array(z.string()).min(1, …)`, refusing only the empty list and
judging no member, and `transportProjectionArray` refuses `[]` for
`fields` / `select`. Both say why: absent means "all", so an empty list
read as absent is a widening. `names` follows `searchFieldsArray`
exactly: `.min(1)`, no member check. It is a minimum, not a refinement,
so the published JSON Schema states it (`minItems: 1`) and
`dropped-refinements.baseline.json` does not move. The door's `400` for
`?names=` stays the server PR's (it is in the ruling's server workload).
**Generated artifacts.** `pnpm --filter @objectstack/spec build` then
`check:generated` named one stale artifact,
`content/docs/references/**`; `gen:docs` regenerated it (one table row
in `content/docs/references/api/protocol.mdx`). The build's `gen:schema`
added `api/GetMetaItemsRequest:names` to
`packages/spec/authorable-surface/api.json`. `api-surface/`,
`export-origins/` and `declaration-map/` did not move: no export is
added, removed or renamed (`GetMetaItemsRequestSchema` and
`GetMetaItemsRequest` exist already). `check:generated` then reported
all 14 artifacts current.
**The declaration lines differ from the card's `Metadata-protocol:
none`.** Measured: `scripts/check-changeset-no-major.mjs` refuses `none`
with any `minor` entry (verdict `none-minor`) and refuses `none`
whenever an `authorable-surface/` shard moves (verdict
`none-baseline-moved`). This diff moves `authorable-surface/api.json`,
so `none` cannot pass at any level, and the ruling's "spec minor" needs
a kind. The two PRs that last moved that shard with an `api` key, #29521
(`45b4922d`) and #29100 (`f1d795b1`), declared `Clause-②: yes
(widening)` + `Metadata-protocol: added` + `@objectstack/spec` minor.
This PR declares the same. Seat 2 agreed and amended the claim on
#29441. The `Clause-②:` line keeps the card's `(widening)` arm.
**Changeset.** `.changeset/29441-meta-items-names.md`:
`@objectstack/spec` minor, 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 carrying `names` is
answered as if it were absent.
## Tests
`packages/spec/src/api/protocol.test.ts` gains
``describe('GetMetaItemsRequestSchema declares `names` — the batch
by-names list read')``, beside the file's other meta-read request
blocks, with 7 cases:
- accepts `names` and preserves the list through parse (the parsed value
is asserted, since the object is non-strict and a stripped key also
reads `success`);
- the absent-`names` control: a request without it parses and parse
invents no `names`;
- refuses `[]` with one `too_small` issue at path `['names']`;
- judges no member: `['no_such_app']`, `['']` and `['crm', 'crm']` parse
and keep their value;
- refuses the wire spelling (`'crm,helpdesk'`) and a non-string member;
- `z.toJSONSchema` states `names` as `{ type: 'array', minItems: 1,
items: { type: 'string' } }` and not required;
- a type-level pin: `GetMetaItemsRequest['names']` is `string[] |
undefined` (input and output alike, which also keeps
`Iso_api_protocol__GetMetaItemsRequestSchema` in
`type-alias-convention.pin.test.ts` true).
<details><summary>Runs</summary>
All under `scripts/pm/os-verify-lock.sh` (shared box; wall-clock figures
are not idle-box figures).
- On the final head `064d4e01d` (after the `origin/main` merge): `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-typecheck` OK). `pnpm --filter @objectstack/spec test:repo`:
`Test Files 89 passed (89)`, `Tests 1522 passed (1522)`.
- On the pre-merge head `c968b47f`: spec `test` 802/802 files, 23739
tests passed; `typecheck` exit 0; `test:repo` 89/89 files, 1522 tests
passed.
</details>
<details><summary>Ablation (one, on the tested behaviour)</summary>
Committed head `c968b47f`, in a separate detached worktree so the gate
run was not disturbed. Spec suites import the schema by relative `src`
path, so no build or dist preflight is involved.
- Control leg (unmutated): `vitest run src/api/protocol.test.ts -t
'batch by-names'`: `Tests 7 passed | 230 skipped (237)`.
- Mutation via `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 reports `anchor x1 -> x0`, `replace
x0 -> x1`, blob `e8475ac80907 -> e261fbcb5c77`.
- Result: `Tests 2 failed | 5 passed`. Failed: "refuses an EMPTY list,
at `names`" and "states the minimum in the published JSON Schema"
(`minItems: 0` received). Expected direction: red.
- Restore: blob after restore `e8475ac80907` = HEAD blob, `git diff
HEAD` empty, marker count 0.
</details>
## Acceptance notes
- Declared before enforced, by the ruling's order: until the server PR
on #29236 lands, `getMetaItems` in `@objectstack/metadata-protocol`
(inline parameter type `{ type; packageId?; previewDrafts? }`,
`protocol-meta-list-reads.ts:54`) does not read `names`, and the
`/meta/:type` list 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.
- The schema judges no member. Whether the door refuses a blank segment
such as `?names=a,,b` is 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, the `cascadeDeleteObjects` describes
≈:646/:767/:2480/:2501) touches a different region; the later lander
merges `origin/main` and regenerates.
## Verification
Local subset, on the final head `064d4e01d` (merge of `origin/main`
`3d5933a97`); 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 base `3d5933a97`), 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".
- On the pre-merge head `c968b47f` the same 102 ran: 96 exit 0 first
pass, 6 exit 3 (`PREREQUISITE NOT MET`: unbuilt `@objectstack/lint` /
`@objectstack/formula` dist and the dist-reading gates), all 6 exit 0
after a full `turbo run build` (117/117); `--ran` reconciled 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-protocol `added`". Control leg, the same body with
`Metadata-protocol: none`: exit 1, `none-baseline-moved` ("Moved:
packages/spec/authorable-surface/api.json").
- `pnpm --filter @objectstack/spec check:generated` after 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.
- No export was renamed or removed, so `check-platform-checklist
--changed-from` is not owed.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01WGufzik1dDv4DzrV1fWBV9)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent cba12d2 commit 6f09464
5 files changed
Lines changed: 121 additions & 0 deletions
File tree
- .changeset
- content/docs/references/api
- packages/spec
- authorable-surface
- src/api
| 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 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1514 | 1514 | | |
1515 | 1515 | | |
1516 | 1516 | | |
| 1517 | + | |
1517 | 1518 | | |
1518 | 1519 | | |
1519 | 1520 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
835 | 835 | | |
836 | 836 | | |
837 | 837 | | |
| 838 | + | |
838 | 839 | | |
839 | 840 | | |
840 | 841 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1872 | 1872 | | |
1873 | 1873 | | |
1874 | 1874 | | |
| 1875 | + | |
| 1876 | + | |
| 1877 | + | |
| 1878 | + | |
| 1879 | + | |
| 1880 | + | |
| 1881 | + | |
| 1882 | + | |
| 1883 | + | |
| 1884 | + | |
| 1885 | + | |
| 1886 | + | |
| 1887 | + | |
| 1888 | + | |
| 1889 | + | |
| 1890 | + | |
| 1891 | + | |
| 1892 | + | |
| 1893 | + | |
| 1894 | + | |
| 1895 | + | |
| 1896 | + | |
| 1897 | + | |
| 1898 | + | |
| 1899 | + | |
| 1900 | + | |
| 1901 | + | |
| 1902 | + | |
| 1903 | + | |
| 1904 | + | |
| 1905 | + | |
| 1906 | + | |
| 1907 | + | |
| 1908 | + | |
| 1909 | + | |
| 1910 | + | |
| 1911 | + | |
| 1912 | + | |
| 1913 | + | |
| 1914 | + | |
| 1915 | + | |
| 1916 | + | |
| 1917 | + | |
| 1918 | + | |
| 1919 | + | |
| 1920 | + | |
| 1921 | + | |
| 1922 | + | |
| 1923 | + | |
| 1924 | + | |
| 1925 | + | |
| 1926 | + | |
| 1927 | + | |
| 1928 | + | |
| 1929 | + | |
| 1930 | + | |
| 1931 | + | |
| 1932 | + | |
| 1933 | + | |
| 1934 | + | |
| 1935 | + | |
| 1936 | + | |
| 1937 | + | |
| 1938 | + | |
| 1939 | + | |
| 1940 | + | |
| 1941 | + | |
| 1942 | + | |
1875 | 1943 | | |
1876 | 1944 | | |
1877 | 1945 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
238 | 238 | | |
239 | 239 | | |
240 | 240 | | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
241 | 265 | | |
242 | 266 | | |
243 | 267 | | |
244 | 268 | | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
245 | 280 | | |
246 | 281 | | |
247 | 282 | | |
| |||
0 commit comments