Repository navigation
Commit 0c47e87
feat(metadata): the layered read takes the bundle member selector (#26861)
<!-- claude-projects-attribution: {"github_login":"os-support-ai"} -->
_Requested by **Yibo Wang** · [project
thread](https://claude.ai/code/project/chan_01BVUyyPie8gHwoPM18iABwB?thread=cmsg_01BVUyyPie8gHwoPM18iABwB8p7MBsAnbgGywykCisiqcv)_
Fixes #26699
#26649 stays open: it keeps the ui half (the Setup row link, the Locale
column, and the editor passing the member to its layered and draft
reads), and stays `pm:blocked` on this PR.
Clause-②: yes
Metadata-protocol: added
Skills-docs: none — no skill or hand-written doc teaches `/layers` or
its query parameters (`git grep -n
"/layers\|getMetaItemLayered\|META_ITEM_LAYERS" -- skills/
.claude/skills/` → 0 hits; the only hand-written docs naming `/layers`
are `content/docs/permissions/system-context.mdx:192` and
`content/docs/ui/apps.mdx:366`, which describe its read gate, not its
parameters)
## 维护者速读
- **改了什么:** Setup 编辑页读的分层视图(`GET
/meta/:type/:name/layers`)现在能按语言读邮件模板的某一种语言(`?member=zh-CN`):代码层、覆盖层、生效值都是那种语言的;不带参数仍是默认语言,行为不变。
- **为什么改:** 多语言邮件模板的第二种语言在 Setup 里打不开(#26649),按 #26649 的裁决
A,服务端这扇门先补上,ui 半边随后。
- **风险与回滚:** 只给一个闭集加一个参数(`package` →
`package`、`member`),两条传输路径共用同一个判定;不存在的语言答 404,不会退回默认语言;回滚即 revert 本 PR。
- **先看:** `packages/metadata-protocol/src/protocol.ts` 的
`getMetaItemLayered`(代码层按成员收窄的那段);`packages/rest/src/meta-item-read-routes.ts`
的 `/layers` 入口(改问共享判定);`packages/rest/src/meta-item-read-gate.ts` 的
`META_ITEM_LAYERS_PARAMS`。
- **两分钟验证:** `pnpm --filter @objectstack/objectql exec vitest run
src/environment-overlay-email-locale-restart.test.ts`,预期 35 个测试全绿,其中 5
个是本 PR 的分层读取用例。
- **需要你做的:** 无,席位落地(席位先做一次契约复审)。
## What changes
- **Spec** (`packages/spec/src/api/protocol.zod.ts`):
`GetMetaItemLayeredRequestSchema` gains `member`, described by the same
`BUNDLE_MEMBER_SELECTOR_DESCRIPTION` the get, delete, publish and
history requests use. Generated:
`content/docs/references/api/protocol.mdx` (one row) and
`packages/spec/authorable-surface/api.json` (one key).
- **Protocol** (`packages/metadata-protocol/src/protocol.ts`,
`getMetaItemLayered`): takes `member`, read through
`resolveBundleMemberSelector` exactly as `getMetaItem` reads it. Every
layer is that member's: the MetadataService copy counts only when it is
the addressed member; the artifact lookup asks for the member
(`lookupArtifactItem(…, wantedMember)`); the registry is asked for a
named member and an answer for another member is dropped; the
package-less address and the served-row resolution get the member. A
member no layer holds answers no layer, which both doors answer `404`.
- **REST** (`packages/rest/src/meta-item-read-gate.ts`,
`meta-item-read-routes.ts`, `rest-server.ts`): `META_ITEM_LAYERS_PARAMS`
is `['package', 'member']`, and the `layers` door's arity list carries
`member`. RestServer's `/layers` route now asks
`metaItemQueryRefusal('layers', …)` (ruling 6138588561, letter B), as
its item read and the runtime dispatcher's `/layers` already do, writing
the nested `{ error: { code, message } }` its `refuseUnknownQueryParams`
call wrote. `serveMetaItemLayered` spreads
`metaMemberSelector(req.query)` into the request.
- **Runtime** (`packages/runtime/src/domains/meta.ts`): the dispatcher's
`/layers` hands `metaMemberSelector(query)` to `answerMetaLayered`,
which spreads it into `getMetaItemLayered`.
- **Changeset**: `.changeset/26699-layered-read-member.md`, `minor` on
spec, metadata-protocol, rest and runtime, with both declaration lines.
## Tests
-
`packages/objectql/src/environment-overlay-email-locale-restart.test.ts`
(real engine, real protocol), new `[#26699]` block: the layered read of
`zh-CN` answers `code: 'PKG zh'`, `overlay: 'ADMIN zh'`; a member nobody
stored answers its own code layer and no overlay; CONTROL the canonical
read (absent and `en-US`) answers the canonical layers, and the whole
answer for `member: 'en-US'` equals the bare one; `fr-FR` answers no
layer; a member on `view` and a blank one are the located `400
VALIDATION_ERROR` (status and code asserted). A protocol over the same
engine with a MetadataService double whose copy is `zh-CN`: the
canonical read (absent and `en-US`) reports `code: 'PKG en'`, never the
service's other member, and the `zh-CN` read reports the service copy
(`SVC zh`).
- `packages/runtime/src/domains/meta-item-query-decision.test.ts`, the
`/layers` block on both transports: an unrecognised name is the same
located 400 on both (`Supported parameters: member, package.`); a
repeated `member` is the same refusal on both and the protocol is never
called; `?member=zh-CN` reaches `getMetaItemLayered` with the package
beside it on both, and no selector sends no `member` key; `fr-FR` is
`404` on both; a member on `view` reaches the real protocol and comes
out as `400 VALIDATION_ERROR` with its sentence on both.
- Pins updated: `packages/rest/src/meta-item-query-decision.test.ts`
(closed-set pin, the layers door now passes `member`, a repeated
`member` refused),
`packages/rest/src/rest-server-meta-door-closed-sets.test.ts` (`/layers`
left the member-refusing list; its unknown-name refusal stays pinned),
`packages/metadata-protocol/src/protocol.lookup-artifact-item-call-sites.test.ts`
(the layered code-layer lookup's recorded arguments now carry
`wantedMember`).
## Acceptance notes
- **The canonical read, one narrow change.** With no member named, the
code layer's MetadataService copy now counts only when it is the
canonical member, as `getMetaItem`'s does (#22877). For every type
without members nothing moves. For an `email_template` whose service
copy is a non-canonical member (the service keys by name, so it holds
whichever member registered last), the canonical layered read used to
report that member as `code`; it now falls to the canonical artifact,
the body the by-name read serves. The MetadataService-double test in the
objectql file pins this (ablated below).
- **The deprecated `?layers=` spelling is unchanged.** It still refuses
a `member` beside it, with the sentence pinned byte for byte ("Read one
member with `?member=` alone."). That remedy could now also name
`/layers?member=`; left as is to keep the pinned answer, and the
spelling is on its way out.
- **SDK.** `MetadataClient.layered()` lives in
`ui/packages/data-objectstack` and takes `{ packageId }` only. The
editor needs `member` there, and that is #26649's ui half per its
ruling, so it is not touched here.
- **Declarations.** Both lines are copied from the claim (6448343327).
Two readings for the seat: the claim writes `Clause-②: yes` without an
arm, while the change is a widening (`yes (widening)` would also be
accurate); and the director's ruling record on #26649 (6444406836)
spelled this card `Metadata-protocol: none`, since a REST query
parameter is not something a metadata author writes, while the claim
says `added`.
## Verification
Readings at head `9db80b0d3` (main merged at `1a8b7ebef`) unless a line
names an earlier head; on a shared container.
<details><summary>Ablation: the layered overlay read without the
member</summary>
Mutation through `scripts/ablation-replace.mjs` (anchor 1 → 0, blob
`09617268` → `663e2627`): drop `...memberOption(member)` from the
layered read's `findServedOverlayRow` call.
`@objectstack/metadata-protocol` rebuilt; `ablation-dist-preflight`
found the marker in `dist/index.cjs` and `dist/index.js`.
`environment-overlay-email-locale-restart.test.ts`: 2 failed / 32 passed
(the `zh-CN` layers and the `fr-FR` absence: both got the canonical
row). Restored (`git diff HEAD` empty, blob equal to HEAD), rebuilt,
preflight `--absent` exit 0, then 34/34 passed. Taken at `c71460ec1`.
</details>
<details><summary>Ablation: the layered code layer without the member
check on the MetadataService copy</summary>
Mutation through `scripts/ablation-replace.mjs` (anchor 1 → 0): the
layered read's `if (isWantedMember(fromService.data))` becomes an
always-true test carrying the marker `ABLATION_26699`.
`@objectstack/metadata-protocol` rebuilt; `ablation-dist-preflight`
found the marker in `dist/`.
`environment-overlay-email-locale-restart.test.ts`: 1 failed / 34 passed
(the MetadataService-double test: expected `PKG en`, received `SVC zh`).
Restored (blob equal to HEAD), rebuilt, preflight `--absent` exit 0 with
a clean tree, then 35/35 passed. Taken at `5cb644cdc`.
</details>
<details><summary>Tests (under os-verify-lock)</summary>
At `9db80b0d3`:
- `@objectstack/rest` vitest: Test Files 333 passed (333), Tests 6792
passed / 344 skipped.
- `@objectstack/runtime` vitest: Test Files 464 passed (464), Tests 8418
passed / 19 skipped.
- `@objectstack/metadata-protocol` vitest: Test Files 270 passed / 4
skipped (274), Tests 21666 passed / 26 skipped.
- `@objectstack/plugin-security` vitest: Test Files 231 passed (231),
Tests 5032 passed / 45 skipped (it calls `getMetaItemLayered` at
`packaged-permission-set-lock-gate.ts:83` and
`security-catalog-echo.ts:194`, both without a member).
- `@objectstack/objectql`
`environment-overlay-email-locale-restart.test.ts`: 35 passed (35).
- `typecheck` on spec, metadata-protocol, rest, runtime and objectql:
VERDICT command-exit 0.
- `@objectstack/spec check:generated`: all 14 generated artifacts up to
date; the merge needed no regeneration.
At `c71460ec1`: `@objectstack/spec` vitest, Test Files 814 passed (814),
Tests 23320 passed / 1 todo; `@objectstack/plugin-security` vitest, 231
passed (231), Tests 5029 passed / 45 skipped (CI's Test Core (6/8) red
on that head did not reproduce; see comment 6452915227).
</details>
<details><summary>Derived gates (dispatch-gates --commands, 109
families)</summary>
Taken before the main merge at `1a8b7ebef`, on the branch's own changes.
`node scripts/pm/dispatch-gates.mjs --ran FILE --repo
objectstack-ai/objectstack`: 109 derived, 107 run, 2 NOT-MEASURED, 0
UNRUN; exit 0.
- 107 families exit 0.
- NOT MEASURED, exit 3 (PREREQUISITE NOT MET: they read a whole-repo
build): `pnpm --filter @objectstack/spec run check:skill-examples`,
`pnpm check:dual-build-cjs-loads`. CI runs both on the PR.
</details>
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01WGufzgNjQhS286u5WaoeU8
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 5ce3404 commit 0c47e87
14 files changed
Lines changed: 311 additions & 53 deletions
File tree
- .changeset
- content/docs/references/api
- packages
- metadata-protocol/src
- objectql/src
- rest/src
- runtime/src/domains
- 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 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1380 | 1380 | | |
1381 | 1381 | | |
1382 | 1382 | | |
| 1383 | + | |
1383 | 1384 | | |
1384 | 1385 | | |
1385 | 1386 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
101 | 101 | | |
102 | 102 | | |
103 | 103 | | |
104 | | - | |
105 | | - | |
| 104 | + | |
| 105 | + | |
106 | 106 | | |
107 | 107 | | |
108 | 108 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
11400 | 11400 | | |
11401 | 11401 | | |
11402 | 11402 | | |
| 11403 | + | |
| 11404 | + | |
11403 | 11405 | | |
11404 | 11406 | | |
11405 | 11407 | | |
| |||
11446 | 11448 | | |
11447 | 11449 | | |
11448 | 11450 | | |
| 11451 | + | |
| 11452 | + | |
| 11453 | + | |
| 11454 | + | |
| 11455 | + | |
| 11456 | + | |
| 11457 | + | |
| 11458 | + | |
| 11459 | + | |
| 11460 | + | |
| 11461 | + | |
| 11462 | + | |
11449 | 11463 | | |
11450 | 11464 | | |
11451 | 11465 | | |
| |||
11462 | 11476 | | |
11463 | 11477 | | |
11464 | 11478 | | |
| 11479 | + | |
| 11480 | + | |
| 11481 | + | |
| 11482 | + | |
| 11483 | + | |
11465 | 11484 | | |
11466 | 11485 | | |
11467 | 11486 | | |
11468 | 11487 | | |
11469 | 11488 | | |
11470 | 11489 | | |
11471 | | - | |
11472 | | - | |
11473 | | - | |
| 11490 | + | |
| 11491 | + | |
| 11492 | + | |
| 11493 | + | |
| 11494 | + | |
11474 | 11495 | | |
11475 | 11496 | | |
11476 | 11497 | | |
| |||
11509 | 11530 | | |
11510 | 11531 | | |
11511 | 11532 | | |
| 11533 | + | |
| 11534 | + | |
| 11535 | + | |
| 11536 | + | |
| 11537 | + | |
| 11538 | + | |
| 11539 | + | |
| 11540 | + | |
| 11541 | + | |
11512 | 11542 | | |
11513 | 11543 | | |
11514 | | - | |
11515 | | - | |
| 11544 | + | |
| 11545 | + | |
| 11546 | + | |
| 11547 | + | |
| 11548 | + | |
11516 | 11549 | | |
11517 | 11550 | | |
11518 | | - | |
| 11551 | + | |
11519 | 11552 | | |
11520 | 11553 | | |
11521 | 11554 | | |
| |||
11558 | 11591 | | |
11559 | 11592 | | |
11560 | 11593 | | |
11561 | | - | |
| 11594 | + | |
11562 | 11595 | | |
11563 | 11596 | | |
11564 | 11597 | | |
| 11598 | + | |
11565 | 11599 | | |
11566 | 11600 | | |
11567 | 11601 | | |
11568 | 11602 | | |
11569 | 11603 | | |
| 11604 | + | |
11570 | 11605 | | |
11571 | 11606 | | |
11572 | 11607 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
497 | 497 | | |
498 | 498 | | |
499 | 499 | | |
| 500 | + | |
| 501 | + | |
| 502 | + | |
| 503 | + | |
| 504 | + | |
| 505 | + | |
| 506 | + | |
| 507 | + | |
| 508 | + | |
| 509 | + | |
| 510 | + | |
| 511 | + | |
| 512 | + | |
| 513 | + | |
| 514 | + | |
| 515 | + | |
| 516 | + | |
| 517 | + | |
| 518 | + | |
| 519 | + | |
| 520 | + | |
| 521 | + | |
| 522 | + | |
| 523 | + | |
| 524 | + | |
| 525 | + | |
| 526 | + | |
| 527 | + | |
| 528 | + | |
| 529 | + | |
| 530 | + | |
| 531 | + | |
| 532 | + | |
| 533 | + | |
| 534 | + | |
| 535 | + | |
| 536 | + | |
| 537 | + | |
| 538 | + | |
| 539 | + | |
| 540 | + | |
| 541 | + | |
| 542 | + | |
| 543 | + | |
| 544 | + | |
| 545 | + | |
| 546 | + | |
| 547 | + | |
| 548 | + | |
| 549 | + | |
| 550 | + | |
| 551 | + | |
| 552 | + | |
| 553 | + | |
| 554 | + | |
| 555 | + | |
| 556 | + | |
| 557 | + | |
| 558 | + | |
| 559 | + | |
| 560 | + | |
| 561 | + | |
| 562 | + | |
| 563 | + | |
| 564 | + | |
| 565 | + | |
| 566 | + | |
| 567 | + | |
500 | 568 | | |
501 | 569 | | |
502 | 570 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
35 | 35 | | |
36 | 36 | | |
37 | 37 | | |
38 | | - | |
| 38 | + | |
39 | 39 | | |
40 | | - | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
41 | 43 | | |
42 | 44 | | |
43 | 45 | | |
| |||
76 | 78 | | |
77 | 79 | | |
78 | 80 | | |
79 | | - | |
80 | | - | |
| 81 | + | |
| 82 | + | |
81 | 83 | | |
82 | 84 | | |
83 | 85 | | |
| 86 | + | |
| 87 | + | |
84 | 88 | | |
85 | 89 | | |
86 | 90 | | |
| |||
92 | 96 | | |
93 | 97 | | |
94 | 98 | | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
95 | 103 | | |
96 | 104 | | |
97 | 105 | | |
| |||
0 commit comments