Symptom
A flat view body — a single view's config written where the container belongs — is accepted silently by the runtime metadata door.
PUT /api/v1/meta/view/<name>?mode=draft with {name, type:'grid', columns:[…], data:{…}} → 200state:'draft' (and 200state:'active' on the direct door)- publish → 200
- read-back carries
_diagnostics:{valid:true}
Reproduced on 3 names. The guidance for this exact body exists and fires elsewhere: defineView(body) on the same body throws a located message — "Unrecognized key(s) on this view container: type, columns, data. • type belongs to a single VIEW, not to the container. Wrap it: defineView({ list: { … } })".
Consequence measured:expandViewContainer(...) → [], and GET /meta/view?object=… omits it, while GET /meta/view lists it as a bare unbound row. Registered, reported valid, renders nothing — precisely the outcome the strict container shape was closed to prevent.
Root cause
Two schemas judge the same body, and the write path is on the lenient one.
getMetadataTypeSchema('view') resolves to ViewMetadataSchema (packages/spec/src/kernel/metadata-type-schemas.ts:102), a 4-member wire union (packages/spec/src/ui/view.zod.ts, ~line 2941 on the run's build). Its precondition assertViewIdentity passes anything that speaksViewVocabulary accepts — and the message it would otherwise emit names "an inline view config (type, columns, sections, filter, …)" as a legitimate arm. A body carrying type + columns therefore never reaches a rejection.- The guidance-carrying strict
ViewSchema (view.zod.ts:2327 on origin/main; line 2358 on the run's build) is only on the defineView/build path.
Verified by hand during the run: ui.ViewSchema.safeParse(body).success = false with the guidance; getMetadataTypeSchema('view').safeParse(body).success = true.
Stale-premise check: re-verified on objectstackorigin/main (00e9196). The union, the inline-config arm and the assertViewIdentity precondition are all present; the strict ViewSchema is still build-path only.
⚠️ This needs a direction ruling before implementation
The inline arm looks deliberate — #6391 named the union's members precisely so a consumer could reference them contractually, and #5599 / #5074 sit behind the current shape. The defect as measured is not "the union has four members"; it is that the door accepts a view it then cannot expand, and stamps it valid. Two candidate directions, and picking wrong re-opens a settled contract:
- A — re-word the checklist item / accept the shape as authored. If a bare inline config is a legitimate stored
view, then the bug is that it is reported valid:true while being unreachable, and the fix is on the diagnostics/expansion side, not the schema. - B — make the inline arm require an object binding. An inline config that cannot name the object it attaches to cannot be expanded or served, so requiring the binding turns a silent dead row into a located refusal at the door — at the cost of tightening a published union arm.
Neither is safe to guess. Please rule before this is dispatched.
Related, not duplicate:#5599 (closed) is the ancestor of the current union — it reported that saveMetaItem({item:{nope:1}}) stored a junk active view. That hole was closed by the identity precondition; this card is the residue the precondition deliberately lets through, because a flat view config does speak view vocabulary.
Adjacent and worth reading together: #7736 (this run) — a container body reaches the same door and is stored without ever being expanded either. The two cards meet at the same place: the runtime write door has no expansion step, so neither shape can produce a servable view.
Reproduction
- Boot the showcase with writable runtime packages.
PUT /api/v1/meta/view/<name>?mode=draft with {name:'<name>', type:'grid', columns:[…], data:{…}} → 200 state:'draft'.- Publish → 200;
GET /api/v1/meta/view/<name> → _diagnostics:{valid:true}. GET /api/v1/meta/view?object=<obj> → the view is absent; GET /api/v1/meta/view lists it as a bare unbound row.- Contrast: call
defineView(body) on the identical body — it throws the located "Wrap it: defineView({ list: { … } })" guidance.
Routing note
Filed domain:spec rather than domain:metadata: both located files are in packages/spec (ui/view.zod.ts, kernel/metadata-type-schemas.ts), and every candidate fix direction above edits a schema or its diagnostics. If the ruling lands on A and the work turns out to be expansion-side, re-route to domain:metadata at that point.
Source
Extracted from the QA run #7695 (framework 92f26f7, console 09987b680).
Symptom
A flat view body — a single view's config written where the container belongs — is accepted silently by the runtime metadata door.
PUT /api/v1/meta/view/<name>?mode=draftwith{name, type:'grid', columns:[…], data:{…}}→ 200state:'draft'(and 200state:'active'on the direct door)_diagnostics:{valid:true}Reproduced on 3 names. The guidance for this exact body exists and fires elsewhere:
defineView(body)on the same body throws a located message — "Unrecognized key(s) on this view container:type,columns,data. •typebelongs to a single VIEW, not to the container. Wrap it:defineView({ list: { … } })".Consequence measured:
expandViewContainer(...)→[], andGET /meta/view?object=…omits it, whileGET /meta/viewlists it as a bare unbound row. Registered, reported valid, renders nothing — precisely the outcome the strict container shape was closed to prevent.Root cause
Two schemas judge the same body, and the write path is on the lenient one.
getMetadataTypeSchema('view')resolves toViewMetadataSchema(packages/spec/src/kernel/metadata-type-schemas.ts:102), a 4-member wire union (packages/spec/src/ui/view.zod.ts, ~line 2941 on the run's build). Its preconditionassertViewIdentitypasses anything thatspeaksViewVocabularyaccepts — and the message it would otherwise emit names "an inline view config (type,columns,sections,filter, …)" as a legitimate arm. A body carryingtype+columnstherefore never reaches a rejection.ViewSchema(view.zod.ts:2327onorigin/main; line 2358 on the run's build) is only on thedefineView/build path.Verified by hand during the run:
ui.ViewSchema.safeParse(body).success = falsewith the guidance;getMetadataTypeSchema('view').safeParse(body).success = true.Stale-premise check: re-verified on
objectstackorigin/main(00e9196). The union, the inline-config arm and theassertViewIdentityprecondition are all present; the strictViewSchemais still build-path only.The inline arm looks deliberate — #6391 named the union's members precisely so a consumer could reference them contractually, and #5599 / #5074 sit behind the current shape. The defect as measured is not "the union has four members"; it is that the door accepts a
viewit then cannot expand, and stamps it valid. Two candidate directions, and picking wrong re-opens a settled contract:view, then the bug is that it is reportedvalid:truewhile being unreachable, and the fix is on the diagnostics/expansion side, not the schema.Neither is safe to guess. Please rule before this is dispatched.
Related, not duplicate:#5599 (closed) is the ancestor of the current union — it reported that
saveMetaItem({item:{nope:1}})stored a junk active view. That hole was closed by the identity precondition; this card is the residue the precondition deliberately lets through, because a flat view config does speak view vocabulary.Adjacent and worth reading together: #7736 (this run) — a container body reaches the same door and is stored without ever being expanded either. The two cards meet at the same place: the runtime write door has no expansion step, so neither shape can produce a servable view.
Reproduction
PUT /api/v1/meta/view/<name>?mode=draftwith{name:'<name>', type:'grid', columns:[…], data:{…}}→ 200state:'draft'.GET /api/v1/meta/view/<name>→_diagnostics:{valid:true}.GET /api/v1/meta/view?object=<obj>→ the view is absent;GET /api/v1/meta/viewlists it as a bare unbound row.defineView(body)on the identical body — it throws the located "Wrap it:defineView({ list: { … } })" guidance.Routing note
Filed
domain:specrather thandomain:metadata: both located files are inpackages/spec(ui/view.zod.ts,kernel/metadata-type-schemas.ts), and every candidate fix direction above edits a schema or its diagnostics. If the ruling lands on A and the work turns out to be expansion-side, re-route todomain:metadataat that point.Source
Extracted from the QA run #7695 (framework 92f26f7, console 09987b680).