Uh oh!
There was an error while loading. Please reload this page.
spec: inline view arms of the runtime write door require the object binding - #8089
Conversation
…inding (#7741) Direction B per the maintainer ruling of 2026-08-12: the two flattened overlay members of ViewMetadataSchema now require object + viewKind — the exact pair the object-bound read paths filter on (GET /meta/view?object= in rest-server.ts and getViewsByObject() in metadata-manager.ts both match v.viewKind && v.object === obj) — so an inline config that could never be served is refused at the door, draft and active alike, with located guidance that reuses defineView's existing wrap prescription instead of forking a second copy. Personalization PUTs are unaffected: normalizeViewMetadata inherits viewKind/object/label from the shadowed registry entry (#2555) before validation, so a console pin/sort/hide PUT on a real view arrives bound. The body this refuses is the baseline-less one — the dead row QA run #7695 measured being stored and badged valid:true. Union membership (#6391) is preserved: four arms, same order, same JSON-Schema anyOf face; the arms' required set is the only move. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0123k4cam2jEAkPmbJeoaY3r
Fixture triage across the wire union's consumption radius: every consumer test writing an inline view body through saveMetaItem now carries the object + viewKind binding the arms require — the post-normalize shape a real shadowing write always had. Two deliberate inversions ride along: objectql's 'plain name-stamp with no entry to inherit from' pin (that save IS the #7741 dead row and now 422s with the located guidance, asserting code INVALID_METADATA + status 422), and the diagnostics lean-overlay pin (an unbound stored row is badged invalid now). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0123k4cam2jEAkPmbJeoaY3r
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0123k4cam2jEAkPmbJeoaY3r
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckThis PR changes 1 package(s): 106 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
⛔ 7 release-owned page(s) also reference the affected code. These are read-only:
|
Uh oh!
There was an error while loading. Please reload this page.
Fixes#7741
What
Direction B, per the maintainer ruling of 2026-08-12 on the issue (comment 5261731941): the inline (flattened-overlay) arms of the
viewwire union now require the object binding at the runtime write door. A flat body like{ name, type: 'grid', columns: [...], data: {...} }— which QA run #7695 measured being accepted, published, badged_diagnostics.valid: true, and then served by nothing — is refused at save time with located guidance that reusesdefineView's existing wrap prescription.The binding is a measured pair:
object+viewKindThe ruling requires the inline arm to carry the object binding; the PM dispatch defined "binding" as whatever linkage the read paths need to actually serve the view, measured — no more. Measured on this tree, every object-bound read path filters on both fields, in two call sites with the same predicate:
packages/rest/src/rest-server.ts—GET /meta/view?object=filtersv.viewKind && v.object === objpackages/metadata/src/metadata-manager.ts—getViewsByObject()filtersv.viewKind && v.object === objectRequiring
objectalone would refuse the card's repro and then instruct the author into a second dead row — bound byobject, still invisible to the switcher for want ofviewKind. So both fields are required on the two overlay arms, and only those two: the other identity fields (label,isDefault,order, ...) stay optional because no read path filters on them.How
packages/spec/src/ui/view.zod.tsflattenedViewOverlayFields():objectandviewKindflip from.optional()to required, each with a custom error carrying the located refusal (offending shape named, the serving filter cited, three remedies: bind it, save a ViewItem record, or wrap in a container).VIEW_WRAP_REMEDY) used by both the container guidance (build door) and the new binding refusals (write door) — the ruling requires reuse, not a fork of the prose. The container guidance string is byte-identical to before.anyOfface in both io directions; the arms'requiredset is the only move. A bad key inside a form field'spublicPickerreports the CONTAINER branch's diagnostic — the author is told to fixviewKind/config, not the key they typed #7510's branch focusing makes the refusal located: the claimed inline arm carries the issues at pathsobject/viewKind, the other three arms are muted.packages/spec/src/kernel/metadata-type-schemas.ts: comment documenting that every consumer ofgetMetadataTypeSchema('view')— the saveMetaItem 422 gate and the read-time diagnostics badge — inherits the refusal here, draft and active alike.Why platform writes keep working:
saveMetaItemnormalizes before validating, andviewIdentityPatch(#2555) inheritsviewKind/object/labelfrom the registry entry the overlay shadows — an expanded ViewItem always carries both — so console pin/sort/hide PUTs on real views arrive at the schema already bound. The body that arrives unbound is the baseline-less one: exactly the dead row this closes.Draft = active, per the ruling (「draft 与 active 同样适用 …… 不预留」): the pin lives at the schema layer, which both save modes resolve through the same
getMetadataTypeSchema('view')entry.#7736 not foreclosed: the sibling (container body stored but never expanded) is untouched — the container arm's shape and acceptance are byte-identical, pinned by tests; the write door still has no expansion step, which stays #7736's own card.
Tests
packages/spec/src/ui/view-inline-object-binding.test.ts: the card's exact repro body refused throughgetMetadataTypeSchema('view')with the located guidance asserted (offending shape, serving-filter reason, wrap remedy, paths); half-bindings refused; bound inline body, ViewItem record, and container accepted byte-identically; the post-normalize personalization PUT accepted; JSON-Schemarequiredface pinned in both io directions.INVALID_METADATA+ 422 + guidance + nothing stored) and metadata-protocol's diagnostics lean-overlay pin (an unbound stored row is badged invalid now). The sweep(spec): 2-item "validation diagnostics don't reach the real defect" sweep — one claim, one PR, per-item checklist (#6391 #5389) #7025 / A bad key inside a form field'spublicPickerreports the CONTAINER branch's diagnostic — the author is told to fixviewKind/config, not the key they typed #7510 "acceptance face did not move" corpora keep both generations: bound spellings in ACCEPTED, unbound originals pinned as REFUSED under a[#7741]marker.Verification
7dc106738):@objectstack/spec10116 tests green + typecheck;metadata-protocol1094,metadata603,rest1555,platform-objects347,objectql3356,runtime2171 — all green after triage; example appsapp-crm/app-showcase/app-todoobjectstack validateexit 0.origin/main@f46e987e9(merged in this branch):@objectstack/specfull suite 384 files / 10165 tests green;check:generatedall 13 artifacts current (api-surface regenerated byte-identical; the overlay members deliberately mint no reference/manifest entries per spec/ui: ViewMetadataSchema 的 union 无判别式且容器成员未导出——消费方做失败诊断只能按成员序索引嵌套 errors #6391, so nocontent/docs/references/**diff exists to ride). Post-merge consumer re-run COMPLETE and green:metadata-protocol,rest,objectql,runtime,metadata,platform-objectsall exit 0.check:adr-anchors,check:changeset-gate-self-tests,check:cross-package-test-inputs,check:doc-formula-expressions,check:docs-audit-scope,check:i18n,check:merge-driver,check:release-body,check:spec-parsed-alias,check:nul-bytes.@objectstack/specminor.Residue (deliberate, reported)
valid: falsefrom the diagnostics badge instead of the falsevalid: truereceipt — that is the ruling's point, but operators with legacy dead rows will see the badge flip.showcase_task.default, which always carry the pair).Generated by Claude Code