Repository navigation
Commit 4b7c630
Fixes #22614
Clause-②: no
A `500` is a fault, not a refusal. No accepted set, export or error code
changes. The by-name read of a view container now answers on every boot,
translated.
## The defect, re-measured on this branch's base (`3d0eeefa4`)
One single-package project: an object `acme_note`, a `defineView({
object: 'acme_note', list, listViews: { mine } })` container, and a
`zh-CN` catalog for both views. The read is `GET
/api/v1/meta/view/acme_note`, authenticated as the seeded admin.
| boot | before | after |
| --- | --- | --- |
| artifact (`os build`, then `os serve` of the artifact) | `500
INTERNAL_ERROR`; log: `Cannot read properties of undefined (reading
'startsWith')` | `200`; `list.label` is `全部笔记`, `listViews.mine.label`
is `我的笔记` |
| config (`os serve objectstack.config.ts`) | `200`, but `label:
"acme_note"` is served (no author wrote it), and both views stay `All
Notes` / `My Notes` for a `zh-CN` reader | `200`; the same container as
the artifact boot serves |
| control: `GET /meta/view/acme_note.default`, `/acme_note.mine` |
`200`, translated | unchanged, on both boots |
**Why the two boots served different documents.** Both registrars file
the container under its object. The config boot's ObjectQL boot loop
stamps the derived key onto the item
(`packages/objectql/src/engine.ts:7575`, `toRegister = item.name ===
itemName ? item : { ...item, name: itemName }`). The artifact loader
registers the container as shipped, with no `name`
(`packages/metadata/src/plugin.ts:1198`). The read handed either one to
the spec's single-view translator (`translateMetadataDocument('view',
…)` → `translateView` → `viewTranslationKey`), which keys the catalog on
`view.name`. With no name it threw. With the stamped name it served the
container as ONE view keyed by the object: the name became the label
fallback, and no view inside was translated. The config boot's `200` was
the same fault without the crash.
## The fix: `packages/rest/src/meta-item-read-gate.ts` only
`translateMetaDocument` (both transports call it: `RestServer` and the
runtime dispatcher's `/meta` item read) classifies a `view` document
once, with the spec's own `isAggregatedViewContainer`. A single view
goes to the spec translator, unchanged. A container goes to
`translateMetaViewContainer`, which translates it as a container,
through `translateView` under each part's own key rule:
- **Each view inside it** (`list`, `form`, each `listViews` /
`formViews` entry) is translated as the item the composer expands it
into. That is `expandViewContainer`, the same expansion both registrars
run, so the view keeps its own `name` (`OBJECT.KEY`). Its packaged base
is looked up by that qualified name. This is exactly what the by-name
read of the expanded view is translated with, so the container read and
the expanded read cannot disagree. The pin asserts that parity. The
result lands back in the slot the view came from. The slot mapping
follows the composer's documented order: a `listViews` entry is found by
name (it is the first claim on its key, so it is never renamed). The
default `list` / `form` is the item the composer flags `isDefault`,
which covers a `list` that restates a named list and collapses into it.
A `formViews` entry is found by position among the named forms, which
covers one renamed by a list that took its key.
- **The container's own `label` / `description`** are keyed by the
object it binds (`objects.OBJECT._views.OBJECT`), with the packaged
container as the base. That is the key the config boot already read for
a container whose `name` is its object, and the key the i18n extractor
scaffolds for such a container.
- **Nothing is invented.** `translateView` falls back to a view's `name`
for a missing label. That fallback is dropped (it is not a translation),
which is what removes the config boot's `label: "acme_note"`. With no
catalog string, the SAME document reference comes back.
- ⛔ Not `view.name?.`: that would leave a container untranslated in
silence (triage `6094457162`).
The binding comes from `deriveViewContainerObject`
(`@objectstack/metadata/view-container`, the registry's one spelling,
through its import-free leaf entry). It is not a fifth copy of the
`object → list.data.object → form.data.object → name` chain.
`packages/spec` is untouched, and so is
`packages/rest/src/rest-server.ts` (PR #22628's region): the
classification lives in the gate.
### Packaging: `@objectstack/metadata` moves from `devDependencies` to
`dependencies` in `@objectstack/rest`
What imports it at runtime, measured on the rebuilt
`packages/rest/dist/index.js` at `7c71a9e34` (top-level ESM `import …
from` lines):
- `@objectstack/metadata/view-container`, 2 lines. One is this PR's
import in `meta-item-read-gate.ts`. The other sits in the bundled
`../objectql/dist/index.mjs` section.
- `@objectstack/metadata/errors`, 5 lines, and
`@objectstack/metadata/view-container-name`, 2 lines. These are in the
bundled `../metadata-protocol/src/protocol.ts` and
`../objectql/dist/index.mjs` sections.
So the published package already loaded `@objectstack/metadata` at
module load without declaring it. tsup bundles the dev dependency
`@objectstack/objectql`, reached through `rest-server.ts`'s optional
`await import('@objectstack/objectql')`. That bundled code's own
external imports of `@objectstack/metadata/*` are hoisted into rest's
top-level imports. `packages/objectql/dist/index.mjs` carries the same
three specifiers (`/errors` ×2, `/view-container` ×1,
`/view-container-name` ×1). The manifest graph does not change (the edge
existed as a dev edge), and `check:workspace-manifest-cycles` and
`check:undeclared-dep-imports` both pass.
## Tests
All at `7c71a9e34` (this branch merged with `origin/main` `ee3ae0360`),
unless a line says otherwise.
- **Function pin**,
`packages/rest/src/meta-view-container-i18n.test.ts`: 10 passed. It
covers both registrar shapes translating alike, CONTROL parity with the
expanded by-name reads, nothing invented, the same reference with no
applicable string or no locale, a collapsed default list, a renamed
default list (`default_2`), a renamed named form (`edit_2`) beside the
default `form`, bulk-action defs in their slot, and the per-view
packaged base looked up by qualified name (an explicit override beats
the catalog).
- **Boot pin**,
`packages/cli/test/serve-view-container-read.integration.test.ts`
(integration tier: it spawns `os serve`; tier partition pin 22/22): one
fixture, a real config boot plus `os build` and a real artifact boot. 6
passed. It covers each boot answering `200` with both views translated
for `zh-CN` and no fabricated `label`, authored labels for `en`, the
same container from both boots, and CONTROL expanded reads unchanged on
both. **Before the fix** (rest `dist` built at `3d0eeefa4`): 4 failed, 2
passed. The artifact boot answered 500, the config boot served `All
Notes`, and the two containers differed. The two CONTROL / `en` config
cases stayed green.
- **Ablation**, committed fix at `f63e6a1d1`. The classification's `if
(metaType === 'view')` was mutated through
`scripts/ablation-replace.mjs` (anchor 1 → 0, blob `fa91bb79` →
`cf670c38`).
- Function pin on mutated `src`: 10 of 10 failed.
- `pnpm --filter @objectstack/rest build`, then
`ablation-dist-preflight` reported the marker present in 2 built files.
Boot pin: 4 failed, 2 passed.
- Restore: blob back to `fa91bb79` (equal to `HEAD`), `git diff HEAD`
empty. Rebuild, then the `--absent` preflight passed: marker absent from
all 6 built files, tree clean. Boot pin: 6 passed.
- A first attempt at the dist leg is void: its rebuild never got the
verify lock (exit 99) and its preflight reported the marker absent. It
was stopped and restored, and its run is not counted.
- `pnpm --filter @objectstack/rest typecheck`: exit 0 (including
`check:test-typecheck`, 0 debt).
- `@objectstack/rest` full suite (`vitest run`, both projects), at
`f63e6a1d1`: 275 files, 5180 passed, 327 skipped, exit 0. The merge
brought nothing under `packages/rest`, `packages/metadata` or
`packages/metadata-protocol`. The incoming `objectql/engine.ts` change
is file-field hydration. So at `7c71a9e34` the pins, the typecheck and
the runtime consumers below were re-run, not the full suite.
- Runtime dispatcher consumers of `translateMetaDocument`
(`packages/runtime/src/domains/meta-item-read-gate-parity`,
`meta-item-envelope`, `meta-list-projection-parity`,
`meta-read-org-scope-parity`, `meta-package-all-fold`): 5 files, 793
passed.
- **Gates**: `dispatch-gates --commands` at `7c71a9e34` derived 76
families. All 76 were run, and every exit code is 0. `--ran`
reconciliation: 76 derived, 76 run, 0 NOT-MEASURED, 0 UNRUN. Two
families needed a second run:
- `check:dual-build-cjs-loads`: the first run reported `PREREQUISITE NOT
MET` (9 unbuilt dists). The rerun passed (107 require entries across 66
packages load).
- `check:query-options-erasure`: its self-test passed, but the first
run's 420 s cap cut the ratchet. The rerun passed in 406 s.
- **Lint**, a proven narrowing (CI runs the full `pnpm lint`), at
`7c71a9e34`: `eslint --no-inline-config --format json packages/rest/src
packages/cli/test/serve-view-container-read.integration.test.ts` exited
0. It linted 303 files, ignored 0, with 0 errors and 0 warnings, and all
three touched `.ts` files are in the linted set.
- Population: `eslint.config.mjs`'s own `files` globs
(`packages/**/*.{ts,tsx,mts,cts}`, `**/*.{ts,…}`). No file reported as
ignored.
- Invariance: the config enables no type-aware linting. All 8
`parserOptions` are `{ ecmaVersion, sourceType }`, with no `project`.
Its only cross-file reads are its own two baselines, which this diff
does not touch. So no untouched file's verdict can move.
## Acceptance notes
- **Known limit.** A container saved at runtime on ANOTHER package's
object is expanded by the metadata protocol under its own name
(`OBJECT.NAME.KEY`, `expandUnderOwnName`), which the gate does not
reproduce. Its views are translated under the composer's names. This is
stated in the function's docblock.
- **The list read is untouched.** `getMetaItems` filters containers out
of every `view` list (`protocol.ts`, `isAggregatedViewContainer`), so no
container reaches `translateMetaList`. Measured: `GET
/meta/view?object=acme_note` answered `200` with only the expanded items
on both boots, at base.
- **The pin's home.** No artifact-vs-config parity file exists on `main`
yet. PR #22612's `serve-config-boot-residual.integration.test.ts` is in
flight, so this is a sibling file built on the same helpers.
- **PR #22612.** It changes how a MULTI-package config boot registers
its unowned top level, through
`MetadataPlugin.registerUnclaimedTopLevel`, which registers residual
containers as shipped (no `name`). It reads only the list, so its read
of such a container's by-name door is not covered there. This fix covers
that shape too, since the container is classified by shape and not by
`name`. This fixture is single-package (no `packages[]`), and #22612's
own CONTROL pins a single-package config as unchanged. I checked this by
reading its diff, not by a merged run (NOT MEASURED: a boot of this pin
on a merge of #22612's head).
- **Observation, not filed** (carrier: none). Rest's `dist` bundles its
dev dependency `@objectstack/objectql` (and metadata-protocol sections),
about 107k lines. It is reached through the `try { await
import('@objectstack/objectql') } catch { … }` in `rest-server.ts`,
whose comment says a deployment without the data engine "must degrade
rather than fail to load". With the bundle, that import never fails, and
a second copy of the objectql module ships inside rest. No wrong answer
was reproduced.
- **Observation, not filed** (carrier: none). `i18n-extract.ts`'s
top-level view walk sends a container that carries its own `name` down
the single-view branch. It scaffolds `_views.NAME` and none of its
views' keys, so a named container's `_views.default` / `listViews` keys
are never scaffolded. Not exercised through a public door here.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01HGa2XMKBRUqPrZFxQavBwH)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
1 parent e250d8f commit 4b7c630
6 files changed
Lines changed: 722 additions & 4 deletions
File tree
- .changeset
- packages
- cli/test
- rest
- src
| 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 | + | |
| 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 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 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 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
| 297 | + | |
| 298 | + | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
| 306 | + | |
| 307 | + | |
| 308 | + | |
| 309 | + | |
| 310 | + | |
| 311 | + | |
| 312 | + | |
| 313 | + | |
| 314 | + | |
| 315 | + | |
| 316 | + | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
| 322 | + | |
| 323 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
29 | 29 | | |
30 | 30 | | |
31 | 31 | | |
| 32 | + | |
32 | 33 | | |
33 | 34 | | |
34 | 35 | | |
| |||
40 | 41 | | |
41 | 42 | | |
42 | 43 | | |
43 | | - | |
44 | 44 | | |
45 | 45 | | |
46 | 46 | | |
| |||
0 commit comments