Repository navigation
fix(spec)!: the view write door refuses an unknown top-level key - #26851
Conversation
…evel key Claude-Session: https://claude.ai/code/session_01WGufziYCUHZQE3MXucvWe9 Co-authored-by: Claude <noreply@anthropic.com>
…he fixtures that froze the strip Claude-Session: https://claude.ai/code/session_01WGufziYCUHZQE3MXucvWe9 Co-authored-by: Claude <noreply@anthropic.com>
…; changeset Claude-Session: https://claude.ai/code/session_01WGufziYCUHZQE3MXucvWe9 Co-authored-by: Claude <noreply@anthropic.com>
…iew.zod.ts strip sites 4 → 2 in the strictness ledger Claude-Session: https://claude.ai/code/session_01WGufziYCUHZQE3MXucvWe9 Co-authored-by: Claude <noreply@anthropic.com>
…r moves STILL_STRIP empties now that the registered view schema is closed; the authoring lint's view divergence pin asserts the refusal instead of the old strip finding; the save-path fixtures that froze a dropped undeclared key now pin its refusal by name. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WGufziYCUHZQE3MXucvWe9
…em envelope The view write door now refuses a key the envelope does not declare, and data is declared only inside config there. A flat list view keeps its default data. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WGufziYCUHZQE3MXucvWe9
📓 Docs Drift Check10 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run. What this run could not see
Coarse fallback — 147 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 4d94c7e46b1eac043e43f20cb63b272a84efa079 && git checkout 4d94c7e46b1eac043e43f20cb63b272a84efa079
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin e78d7057d547e7d57b64e551432b018f2453e322 5b87033b5ad58bab6669a375530d0aff85d199db && git checkout -B drift-repro e78d7057d547e7d57b64e551432b018f2453e322 && git merge --no-ff 5b87033b5ad58bab6669a375530d0aff85d199db
node scripts/docs-audit/affected-docs.mjs --json e78d7057d547e7d57b64e551432b018f2453e322 |
|
Test UI (12/16) is red on 66637ae at Generated by Claude Code |
Contract reviewServed-tier: Reviewed against card #26711 (body, triage 6444389072, claim 6447314891, os-dev-report 6450470105, claim amendment 6450527026), PR #26851 (body, 19-file list, ① Derived judgmentsThe narrowing itself: right. Every key a shipped writer or the console sends is still accepted: right, measured on the writers. I read every non-test view write in
STILL_STRIP now empty: right. Live tombstones keep their prescriptions: right.
The moved pins measure consequences, not bending. Each reversed assertion now checks the refusal's shape, not just Metadata-protocol consumer-side strips: right. ② Semver level
③ Boundary flags
④ Design judgment
Must-fix: move Everything else in this PR holds; a re-review of the moved file is a diff read of one path, and this record's ① and ④ judgments carry over to the head that moves it. Implemented-by: VERDICT: FAIL |
…oot changeset directory Every @object-ui package releases through the root .changeset; the imported ui/.changeset is read by nothing. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WGufziYCUHZQE3MXucvWe9
…r moves The strip-site count for view.zod.ts reads 2 now that the item wire arms closed; the filterableFields absence walk excludes the refusal pin that authors the key on purpose; two objectql save fixtures stop carrying an undeclared key and pin its refusal instead. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WGufziYCUHZQE3MXucvWe9
Contract reviewServed-tier: Delta record over the full review at ① Derived judgmentsThe must-fix is closed by
The merge Every PASS judgment of the full record still holds at this head. The narrowing ( ② Semver level
③ Boundary flags
④ Design judgmentThe delta changes no schema, rule or runtime: one file move and four re-judged pins. Each pin is judged against the code it cites. The ledger pin records what the instrument measures and why the number moved, in the file's own history idiom, so the next count move has to come through the same line. The absence-walk exclusion names the one pin whose job is to author the retired key and says what it proves, which is the kit's own §4 discipline, and leaves the walk's radius and residue untouched. The two objectql pins keep their original assertions (stored round-trip keys; a structured diff of declared keys) and replace the strip finding with the refusal, measured through the real Must-fix: none. Implemented-by: VERDICT: PASS |
席位核对 · ACCEPT ·
|
Requested by Yibo Wang · project thread
Fixes #26711
Clause-②: no (narrowing) — the
viewwrite door refuses a body carrying an undeclared top-level key on a view item record or an inline list or form view, which it saved before without the key. Nothing is widened.Metadata-protocol: narrowed (a saved
viewbody may no longer carry a top-level key its shape does not declare)Skills-docs: none — no page under skills/** or content/docs teaches that the view write door kept an unknown top-level key; the authoring surfaces (defineView / defineViewItem) were already closed
维护者速读
view写入口(ViewMetadataSchema的 view item 记录、内联 list 覆盖、内联 form 覆盖三个成员)从.strip()改为封闭,未知顶层键返回 422unrecognized_keys并点名;container 本来就是封闭的。options.KINDwith the strict per-kind schema and persist the PARSED body — the door half of objectui#10380 #20051 起保存的是解析后的 body,未知键(拼错的、已退役的如striped)会得到 200 但被静默丢弃,与 object / page / dashboard 写入口不一致。unrecognized_keys的旧请求现在会被拒;已测语料(示例 59 个 view、showcase 启动后 36 个已服务 view 的回写)零误伤;唯一受影响的平台写入方是 UI 适配器createView给 envelope 加顶层data,本 PR 已修。回滚 = revert 本 PR。packages/spec/src/ui/view.zod.ts(closeWireMember与三个成员),再看view-wire-unknown-key-refusal.test.ts。pnpm --filter @objectstack/spec exec vitest run --project local src/ui/view-wire-unknown-key-refusal.test.ts,以及pnpm --filter @objectstack/metadata-protocol exec vitest run src/protocol.save-union-issues.test.ts。What changes
ViewMetadataSchemajudges everyviewbody saved throughPUT /api/v1/meta/view/:name(draft or publish), the MCP metadata tools and Studio. It is a union of four members. ThedefineViewcontainer was already closed; the other three stripped their top level:{ name, object, viewKind, config }(ViewItemWireSchema's arms),ListViewOverlayWireSchema),FormViewOverlayWireSchema).Since the save stores the parsed body (#20051), a key one of them did not declare answered
200and was not stored. All three are now closed with the samestrictObject/closedObjectmachinery the other doors use, so the save answers422 INVALID_METADATAwith anunrecognized_keysissue that names the key and the shape:Unrecognized key(s) on this inline list view: `striped`.filter[]/sort[]before the check; the save door removes the read decorations (_diagnostics,_draft) and the server-stamped provenance (_packageId,_packageVersion,_provenance) before it validates, as it did before. Every key the console writes and reads back (VIEW_CONSOLE_ROUND_TRIP_KEYS:isDefault,isPinned,sortOrder,visibility,columnState,_isOverride) is declared on the member it is written on.filterableFields,pageName,tabs,owner,hidden) raise their own prescription, lifted to the union message as before.striped,borderedandvirtualScrollretired in 17.x, and their kits have aged out under the ADR-0087 amendment of 2026-10-26 (spec: retire tombstones, conversions and migration entries older than one protocol major; a strict unknown-key refusal with an upgrade-guide link takes over (maintainer ruling D2 = A) #25767 ruling D2 = A), so they are named and pointed at the protocol upgrade guide, which is the channel that amendment prescribes. I did not re-add tombstones for them, since that would contradict the ruling.objectNamegetsDid you mean `objectName` → `object`?and a top-levelidgetsname.ObjectStackAdapter.createView(@object-ui/data-objectstack) added a top-leveldatato every spec. On a ViewItem envelopedatabelongs insideconfig, so that create would now be refused. It now addsdataonly to a flat list view (patch changeset for@object-ui/data-objectstackunder the root.changeset, which is the only changeset directory the release reads). The console's own create flow goes throughcreateRuntimeMetadata/viewEnvelopeand was not affected.STILL_STRIPinmetadata-type-schemas.test.tsis now empty:viewwas its last entry, and its "end state" note is kept as history with the premise that changed. The authoring lint's view-divergence pin now asserts the registry schema refuses the key, instead of the old strip finding. The strictness ledger row forview.zod.tsis updated and its counts regenerated.Tests
packages/spec/src/ui/view-wire-unknown-key-refusal.test.ts(new, 17 tests) covers each closed member:stripedgets the upgrade-guide sentence;filterableFieldsgets its live tombstone prescription;objectNameandidget their alias guidance.packages/metadata-protocol/src/protocol.save-union-issues.test.tsexercises the realsaveMetaItempath:striped, on an inline list view answers 422INVALID_METADATAand writes 0 rows, and the message names the key;_diagnostics.valid: falsenaming the key.ui/packages/data-objectstack/src/createView.envelopeData-26711.test.ts:createViewon an envelope sends no top-leveldataand the platform schema accepts the body. The control shows that body with the olddatais refused, and a flat list view still gets its defaultdata.view-authoring-wire-split,view-filter-rule-wire-id,view-metadata-schema,view-overlay-viewkind-arm,view-strictness-batch18,view-union-diagnostics,view.test;metadata-type-schemas,metadata-authoring-lint;protocol.graft-folded-form-sections;a3aeacbf:@objectstack/objectqlprotocol-metaandprotocol-publish-rollback(objectNameis now refused namingobject, andextrais gone from the diff fixture); specscripts/strictness-ledger.test(theview.zod.tsstrip count 4 → 2, with a history line); andview-list-filterable-fields-retirement(an EXCLUDED entry for this PR's own refusal pin).Acceptance notes
view的 spec 校验闸门形同虚设:saveMetaItem({ item: { nope: 1 } })返回 success 并把{"nope":1}存成一个 active view #5599 premise changed.view的 spec 校验闸门形同虚设:saveMetaItem({ item: { nope: 1 } })返回 success 并把{"nope":1}存成一个 active view #5599 kept the members'.strip()only while the save stored the original body. After spec(ui)+metadata save: judge a flattened view overlay's top-leveloptions.KINDwith the strict per-kind schema and persist the PARSED body — the door half of objectui#10380 #20051 the save stores the parsed body, and spec(ui)+objectui: declare the console's round-trip keys on the stored view overlay (#20051 stage ii, ruling 甲) #20456 declared the console's round-trip keys, so stripping no longer kept anything; it only lost it silently.options.KINDwith the strict per-kind schema and persist the PARSED body — the door half of objectui#10380 #20051 that still carries an undeclared top-level key is served badged_diagnostics.valid: false, naming the key. This is pinned on the read side. Its next console save is refused, naming the key. That part is inferred from the same parse and was not driven through the console.ef7067fd, the example apps' view barrels (app-crm, app-showcase, app-todo, authored and expanded items) gave 59 bodies. All 59 save before and after the change. With a top-level junk key added, 49 saved before and 0 save after.platform-objectsships no view definitions.Verification
mainef7067fd(the QA ran at3fcafcd3). At schema level, the list overlay, the view item record and the form overlay each accepted a junk key andstriped, and stripped them. The container refused.PUT /api/v1/meta/view/showcase_task.qa_rk_view?mode=draftwith a clean body:200.zzz_junk_keyorstriped:422 INVALID_METADATA, and the issue names the key.a3aeacbf(Test Core 1/8 and 7/8 on66637aeawere this PR's): before the main merge, a locked wide run passed 17 of 17 suites (spec--project repo, objectql, rest, runtime, metadata-fs, metadata-core, cli unit, hono-server, client, audit, mcp, i18n, automation, http-conformance, security, cloud-connection, client-react). On the merged tree (5b87033b),@objectstack/metadata-protocolpassed 270 files / 21672 tests,@objectstack/objectqlpassed 475 / 8843, and speccheck:generatedexits 0.uitest files that reference the view schema exports;check:test-typecheck,check:generatedandcheck:strictness-ledger.Test and gate logs
Merged head, under
scripts/pm/os-verify-lock.sh(shared box):Pre-merge head (same diff), also locked: spec and metadata-protocol typecheck exit 0,
@objectstack/linttest exit 0,@objectstack/metadatatest exit 0,pnpm lintexit 0.Derived gates (
node scripts/pm/dispatch-gates.mjs --commands, 89 commands, each run with its exit code recorded):Six of the 89 first answered exit 3 (prerequisite not met): five spec gates that read
dist(the test-file edits had moved the source hash) andcheck-plugin-teardown-shape --self-test(needs an unshallow clone). They were re-run afterpnpm --filter @objectstack/spec buildandgit fetch --unshallow, and all exit 0.Human Minimum
packages/specholds no runtime logic.pnpm lintpasses.main, one pull request.packages/spectouched: its generated artifacts are regenerated and committed.content/docs/references/,packages/*/CHANGELOG.mdorcontent/docs/releases/.维护者速读block is the maintainer digest the dispatch asks for).Generated by Claude Code
🤖 Generated with Claude Code
https://claude.ai/code/session_01WGufziYCUHZQE3MXucvWe9