Repository navigation
Commit c17719d
feat(rest)!: serve ?names= on the /meta/:type list and close its query set (#30709)
<!-- claude-projects-attribution: {"github_login":"os-elon-musk"} -->
_Requested by **Elon** · [project
thread](https://claude.ai/code/project/chan_01WedDzV26nniwxv7jC88B9D?thread=cmsg_01WedDzV26nniwxv7jC88B9D2RYndRGdbygg7ejR56Dk2B)_
domain:server#3 · os-elon-musk · session_016WV4cyaiN5dg5cw1zSyihP
Fixes #29236
Clause-②: yes (narrowing)
Metadata-protocol: none
## 维护者速读
- 改了什么:`/meta/:type` 列表接口新增 `?names=a,b,c`,一次请求按名字读多个元数据项(`package`
列表除外,它的条目按 id 而非名字区分,带 `?names=` 会返回 400);同时列表只认七个查询参数,拼错或不认识的参数改为返回
400,不再静默返回整张表。
- 为什么改:裁决 A(6580261740)的服务端一半。控制台 Home 的 roster 门现在每个对象发一次请求(app-crm 上 8
次),有了批量读就能并成一次;拼错的过滤参数以前看起来像“匹配了全部”,现在会被明确拒绝。
- 风险与回滚:收窄是唯一的破坏性变化:带未声明参数的脚本会收到 400(控制台和 client SDK
只发已声明参数)。名字过滤在权限门之后、对象字段掩码之前执行,不改变谁能看到什么。回滚即 revert。
- 先看:`packages/rest/src/meta-item-read-gate.ts` 的
`META_LIST_QUERY_PARAMS`、`metaListNames`、列表链第 2a
步;`packages/runtime/src/domains/meta.ts` 的分发器对等实现。
- 两分钟验证:`pnpm --filter @objectstack/rest exec vitest run
src/meta-list-names-filter.test.ts
src/meta-query-arity-doors.test.ts`,预期全部通过。
- 需要你做的:在验收时确认 `locale` 加入封闭集合(裁决原文是六个键,见下方偏差 1);其余无,席位验收、契约复核通过、CI
全绿后落地。
## What changes
**`@objectstack/rest`, the list door `GET /api/v1/meta/:type` (and its
environment-scoped twin).**
- `?names=a,b,c` narrows every type's list to the items of those names,
except `package`'s: a `package` list item is keyed by its id and carries
no `name`, so the `package` list refuses `?names=` with a located `400
VALIDATION_ERROR` (`NAMES_ON_PACKAGE_LIST_MESSAGE`) on both transports,
before any read. One comma-separated value, as the data routes spell
`fields`; each name is trimmed, blanks are dropped and repeats count
once (`metaListNames`). A name that matches no item is simply absent (no
`404`). A value that names no item (`?names=`, `?names=,`) is refused
`400 VALIDATION_ERROR` with `param: 'names'`, before the store read. A
repeated `?names=` is refused like the list's other single-valued keys
(`META_QUERY_ARITY_DOORS.list` gains `names`).
- The narrowing is chain step 2a of `createMetaListAnswer`: after the
per-caller gate (a named item the caller may not see stays withheld) and
before the object field mask and translation, which now run on the named
items only.
- The names are pushed down to the store read as
`GetMetaItemsRequest.names`. The chain step still narrows, so a store
that does not filter answers the same list.
- The list declares its closed query set, `META_LIST_QUERY_PARAMS` =
`package`, `preview`, `object`, `include`, `id`, `names`, `locale`,
judged by the same `unknownQueryParamRefusalMessage` that
`refuseUnknownQueryParams` uses, before the arity check and before any
read (`metaListUnknownParamRefusal` / `metaListQueryRefusal`). Bracketed
keys that already have a located refusal keep it: `package[` everywhere,
`id[` on `app`, `object[` on `view`. Any other bracketed key
(`?names[]=`) is refused as a name the list does not read.
- Three new exports, the decision both transports ask:
`META_LIST_QUERY_PARAMS`, `metaListQueryRefusal`, `metaListNames`.
**`@objectstack/runtime`, the dispatcher twin**
(`packages/runtime/src/domains/meta.ts`): the same recognition and arity
refusal, the same empty-value `400`, and the same push-down to
`protocol.getMetaItems`, all asking the rest exports. The parity census
(`meta-list-projection-parity.test.ts`) derives the read keys from both
sources and now also pins that `META_LIST_QUERY_PARAMS` equals what the
handlers actually read.
**`@objectstack/metadata-protocol`**: `getMetaItems` /
`readFlattenedMetaItems` take `names?: readonly string[]` and keep only
those items once every source is merged, before the app nav
contributions, locks, governance and serving decorations run.
**`@objectstack/client`**: both `meta.getItems` methods (unscoped and
environment-scoped) take `{ names }` and send it as one `?names=a,b`
value.
**`@objectstack/spec`, description only**:
`GetMetaItemsRequestSchema.names` drops its `[PLANNED — not yet served]
` prefix and its trailing "no list door reads it yet" sentence; the
marker test is deleted. `content/docs/references/api/protocol.mdx` is
regenerated (`gen:docs`).
**Docs**: one paragraph in
`content/docs/protocol/kernel/http-protocol.mdx` › List All Objects on
`?names=` and the closed set.
**BREAKING** accept-set narrowing at the metadata list
(`@objectstack/rest`, `@objectstack/runtime`). Before: `GET
/meta/object?name=account`, `?ids=a,b` or `?filter=x` answered `200`
with every object. Now: a name outside the seven is refused `400` with
the nested `{ "error": { "code": "VALIDATION_ERROR", "message" } }` body
naming the parameter and the supported ones, and nothing is read. Who
moves: a script that sends an undeclared name; the Console and the
client SDK send only declared names. Remedy: drop the parameter, or use
`?names=a,b`, `?id=APP`, `?object=OBJECT` or `?package=ID`. The
changeset carries the same section and an `adr-0087: not-required
(no-migration-prescription)` disposition: no schema key moves, and
nothing at rest records the route's answer.
## Why
Ruling A on #29236 (6580261740): one spelling, `?names=a,b,c`, on every
`/meta/:type` list, narrowing after the per-caller gate and before the
object mask, pushed down as `GetMetaItemsRequest.names`, with the list's
closed query set declared in the same server PR. The spec half (#29441,
PR #29722) put `names` on `GetMetaItemsRequestSchema` with the planned
marker; this PR serves it and retires the marker. Measured on `main`
before this PR (6576480506): `?names=`, `?name=`, `?ids=`, `?filter=` on
`/meta/object` each answered the full 115-item, 746 KB list with `200`.
The Console follow-up, folding the Home roster gate's by-name reads into
one `?names=` request, is its own card, #29444 (`domain:ui`). This PR
does not touch `ui/**`.
## Deviations (named, accepted by the seat)
1. **`locale` joins the closed set.** The ruling's line reads: "The same
server PR declares the list door's closed set of query parameters
(`package, preview, object, include, id, names`)". The list door already
reads `?locale=` (`extractLocale` in the translation step, a frame below
the handler), so a six-key set would refuse `?locale=zh-CN`, which
answers `200` today. Keeping it is keeping behaviour, not widening.
Pinned: `?locale=zh-CN` still answers `200` on both transports, and the
census derives `locale` from the source. The seat puts this to the
maintainer at ACCEPT as a veto point.
2. **Patch levels, not minor.** The ruling sketched rest /
metadata-protocol / client minor; `check-changeset-no-major` requires
every entry to be `patch` when the PR declares `Metadata-protocol:
none`, which this is (an API key, not authorable metadata).
3. **The docs paragraph** in `http-protocol.mdx` is outside the ruling's
file list; the seat added it to the claim.
4. **Two existing controls flipped.** In
`meta-app-publish-gate.test.ts`, a bracketed `id[]` on the `view` list
and `object[]` on the `app` list used to answer `200` (ignored); they
are now recognition refusals (`400`), as the closed set requires.
5. **`?names=` is refused on the `package` list** (contract review
6672872746, seat ruling): `package` items carry `manifest.id`, no
`name`, so a names filter there could only answer `200 []`. It is a
located `400` instead; matching packages by id is a different behaviour,
left for its own card if someone needs it. The describe and
`http-protocol.mdx` say "every type except `package`".
## Verification
Main's behaviour is the card's measurement (6576480506) and the
ablations below: with the recognition, the chain step and the push-down
off, the new pins go red the way main answers (the whole list, `200`, no
`names` sent to the store).
- `packages/rest/src/meta-list-names-filter.test.ts` (new): the reading
of `?names=`; the chain step on `object` (the mask and the translation
see only the named items), an unknown name absent, an empty value
rejected before the mask, `app` narrowed after the per-caller gate (a
withheld app stays withheld); the door: push-down, a non-filtering store
on `object` and `view` (and with `?object=`), no `names` sent when
absent, empty `400` with no store read, unknown name `200` with an empty
list.
- `packages/rest/src/meta-query-arity-doors.test.ts`: the closed set,
recognition before arity, every declared key accepted, undeclared and
bracketed keys refused, `?locale=zh-CN` `200`.
- `packages/metadata-protocol/src/protocol.meta-list-names.test.ts`
(new, 5): narrowing on `object` and `app`, nav contributions only for
the named apps, unknown names absent, each named item served as the
whole list serves it.
- `packages/runtime/src/domains/dispatcher-query-arity.test.ts` and
`meta-list-projection-parity.test.ts`: the dispatcher's recognition,
push-down, empty refusal and `locale`, and REST/dispatcher parity on
five new probes (`names` narrowing, `names=nope`, empty `names`,
`names[]`, `?name=crm`).
- `packages/client/src/client.test.ts`: the `?names=` URL, unscoped and
environment-scoped.
- The `package` refusal: `meta-query-arity-doors.test.ts` (the sentence,
every value spelling, recognition outranking it, controls on four other
types, the door answering the nested `400` with no protocol read) and
`dispatcher-query-arity.test.ts` (the dispatcher's twin). Ablated (the
check off): 2 red in rest, 1 red in runtime.
- Ablations, each red: rest recognition off (12), `locale` dropped from
the set (3), chain step off (5), chain step moved after the mask (3),
rest empty refusal off (5), rest push-down off (1); dispatcher
recognition off (census red), dispatcher push-down off, dispatcher empty
refusal off, protocol filter off (4), client `names` off. All green at
head.
- Gates (`dispatch-gates --commands`), first measured at `c2b596eb`
before the main merge: 106 derived families plus the extras and `pnpm
lint`, 112 run, 109 exit 0. `@objectstack/spec` `check:generated` failed
only because `check:test-typecheck` ran out of heap; with an 8 GB heap
it is green at `b0872d29` (all 14 artifacts current).
`check:skill-examples` and `check:dual-build-cjs-loads` exited 3 (NOT
MEASURED: they need a whole-tree build). The full re-run at the merged
head, after a whole-tree build, goes in the os-dev report on the card.
Skills-docs: follow-up #30710
## Human Minimum
- [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic.
- [x] 2. Every name I added uses the casing for its kind.
- [x] 3. No formatter ran; `pnpm lint` passes.
- [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.
- [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_016WV4cyaiN5dg5cw1zSyihP
<!-- signed: domain:server#3 · os-elon-musk ·
session_016WV4cyaiN5dg5cw1zSyihP -->
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 974209d commit c17719d
19 files changed
Lines changed: 850 additions & 49 deletions
File tree
- .changeset
- content/docs
- protocol/kernel
- references/api
- packages
- client/src
- metadata-protocol/src
- rest/src
- runtime/src/domains
- spec/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 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
967 | 967 | | |
968 | 968 | | |
969 | 969 | | |
| 970 | + | |
| 971 | + | |
| 972 | + | |
| 973 | + | |
| 974 | + | |
| 975 | + | |
| 976 | + | |
| 977 | + | |
| 978 | + | |
| 979 | + | |
| 980 | + | |
970 | 981 | | |
971 | 982 | | |
972 | 983 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1517 | 1517 | | |
1518 | 1518 | | |
1519 | 1519 | | |
1520 | | - | |
| 1520 | + | |
1521 | 1521 | | |
1522 | 1522 | | |
1523 | 1523 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
187 | 187 | | |
188 | 188 | | |
189 | 189 | | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
190 | 212 | | |
191 | 213 | | |
192 | 214 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1939 | 1939 | | |
1940 | 1940 | | |
1941 | 1941 | | |
1942 | | - | |
| 1942 | + | |
| 1943 | + | |
| 1944 | + | |
| 1945 | + | |
| 1946 | + | |
1943 | 1947 | | |
1944 | | - | |
| 1948 | + | |
1945 | 1949 | | |
1946 | 1950 | | |
1947 | 1951 | | |
| 1952 | + | |
1948 | 1953 | | |
1949 | 1954 | | |
1950 | 1955 | | |
| |||
8194 | 8199 | | |
8195 | 8200 | | |
8196 | 8201 | | |
8197 | | - | |
| 8202 | + | |
| 8203 | + | |
8198 | 8204 | | |
8199 | 8205 | | |
| 8206 | + | |
8200 | 8207 | | |
8201 | 8208 | | |
8202 | 8209 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
51 | 51 | | |
52 | 52 | | |
53 | 53 | | |
54 | | - | |
| 54 | + | |
55 | 55 | | |
56 | 56 | | |
57 | 57 | | |
| |||
78 | 78 | | |
79 | 79 | | |
80 | 80 | | |
81 | | - | |
| 81 | + | |
82 | 82 | | |
83 | 83 | | |
84 | 84 | | |
| |||
690 | 690 | | |
691 | 691 | | |
692 | 692 | | |
| 693 | + | |
| 694 | + | |
| 695 | + | |
| 696 | + | |
| 697 | + | |
| 698 | + | |
| 699 | + | |
| 700 | + | |
| 701 | + | |
| 702 | + | |
| 703 | + | |
| 704 | + | |
| 705 | + | |
| 706 | + | |
| 707 | + | |
693 | 708 | | |
694 | 709 | | |
695 | 710 | | |
| |||
| 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 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
3814 | 3814 | | |
3815 | 3815 | | |
3816 | 3816 | | |
3817 | | - | |
| 3817 | + | |
| 3818 | + | |
| 3819 | + | |
| 3820 | + | |
| 3821 | + | |
| 3822 | + | |
| 3823 | + | |
3818 | 3824 | | |
3819 | 3825 | | |
3820 | 3826 | | |
| |||
3988 | 3994 | | |
3989 | 3995 | | |
3990 | 3996 | | |
3991 | | - | |
| 3997 | + | |
3992 | 3998 | | |
3993 | 3999 | | |
3994 | 4000 | | |
| |||
0 commit comments