Skip to content

feat(spec)!: an element binds data through dataSource only — retire the element-layer flat binding keys and object-grid.defaultFilters (#11509) - #22421

Open
objectstack-fleet[bot] wants to merge 20 commits into
mainfrom
claude/issue-11509-element-binding-retirement
Open

objectstack-fleet[bot] wants to merge 20 commits into
mainfrom
claude/issue-11509-element-binding-retirement

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #11509

Clause-②: no (narrowing: the ten element-layer flat data-binding keys and object-grid.defaultFilters leave the accept set, and the component-props gate's dataSource.object waiver becomes a refusal)

ADR-0087 disposition, as the changeset's marker carries it: registered element-flat-data-binding-retired, object-grid-default-filters-retired (.changeset/11509-element-flat-binding-retired.md).

Consumer pin rule (triage 6071460039): a consumer that pins both repositories moves its objectui pin past objectui#11880 no later than its objectstack pin moves past this retirement. The converted shape is one only objectui after objectui#11880 reads.

Landing gate: met. The gate is the claim's (6072058120, ruling sub-question 2), moved by the seat (6080485027) to #22476: .objectui-sha must cover the objectui merge that repairs the designer row named in objectstack-ai/objectui#12056.

What this does

Ruling 6051498447 (A-narrow, all four sub-questions at their recommendation), carried out as the claim states it:

  1. Tombstones. element:record_picker object / filter / sort / limit, element:number object / filter, element:repeater object / filter / sort / limit, and object-grid.defaultFilters are retiredKey() tombstones in packages/spec/src/ui/component.zod.ts. Each is refused at its key with the prescription (use dataSource.KEY on the node; what to do where the binding already sets it, by that element's old rule), and its input type is never. The repeater's other spellings (objectName, filters, where, orderBy, sortBy, top, pageSize) were aliases of the flat keys and now point at the binding. The object-* block layer and the relationship-scoped blocks are unchanged.
  2. Conversions (step 18, both retired from the load path, both ordered at 35.5, before page-component-filter-record-to-rule-array at 36).
    • element-flat-data-binding-to-data-source follows each element's old rule: a key the binding lacks moves there; a key the binding already set is deleted where the binding always won (the picker, and element:number's object); element:number's flat filter is appended to the binding's (the two always AND-combined). It reports a TODO and leaves the key in three cases: a picker key beside a dataSource.view that sets no such key of its own, a repeater key the binding sets to a different value or beside a view (the repeater read neither until the console put its binding first, so what it applied depends on the console version), and an element:number filter pair that is not two rule arrays.
    • object-grid-default-filters-removed is the shape defaultSort's retirement took. When filter is empty (absent, null, [] or {}), the rules move into it. When filter has content, the key is deleted. When filter is a value no lowering reads (a bare string), it reports a TODO rather than overwrite it.
    • A record-form value either entry moves reaches the record-form conversion at its new door. So the absorbed arms (the element flat filter and defaultFilters doors of page-component-filter-record-to-rule-array) are dropped from that entry's reach.
  3. Lint. In packages/lint/src/validate-component-props.ts the type-blind suppliedByDataSource waiver is gone. One of the three elements with no dataSource.object is the rule's existing component-props-invalid finding (warning tier, at …dataSource.object); this is not a new rule. The docs gate's twin waiver (packages/spec/scripts/check-yaml-examples.ts) turns with it.
  4. The repeater trap is closed in this PR from both sides. A repeater bound only through dataSource is the clean shape: zero findings, and objectui reads it since objectui#11880. A repeater aimed by a flat object is refused twice: the tombstone at the key and the missing binding. Pinned in validate-component-props.test.ts and in the docs gate's self-test.
  5. Registry and generated outputs. There are eleven RETIRED_KEYS_BY_MAJOR[18] rows. There are two D3 entries (element-flat-data-binding-retired, object-grid-default-filters-retired), each with a step-18 rationale fragment. authorable-surface/ui.json (11 rows [RETIRED]) and content/docs/references/ui/component.mdx are regenerated by the repo's generators. The hand-edited dropped-refinements.baseline.json loses three rows whose only dropped refinement was a retired filter. The committed packages/spec/spec-changes.json and docs/protocol-upgrade-guide.md are regenerated from the merged registries (gen:spec-changes, gen:upgrade-guide); see "This round" below.
  6. The object-tree titleField rationale, corrected (offered by the domain:devx seat, 6093574958 item 2). At objectui 20c6d351a nothing reads a tree titleField: the renderer's getTreeConfig rung went on objectui#8841, and the case 'tree' arm's flatten rung went on objectui#6152 round 14 (objectui 3fd862510), so ListView.tsx:3985 reads labelField: treeCfg.labelField || 'name' and emits no titleField. The OBJECT_TREE_FLAT_CONFIG_GUIDANCE docblock and the last sentence of its prescription said the key is "only ever read as that key's last fallback". Both now say the key stays in the set for its prescription, not for a read, and the prescription still names tree.labelField. The flat key set and every schema shape are unchanged, and the in-test note beside the key-set pin (component.test.ts) gains the same dated reading. Item 1 of that offer (kernel/functional-completeness.ts) is outside this PR's files and is not taken.

Premises, verified on origin/main bb4f5cc00 before the first edit

  • P1 holds. The flat object keys were at component.zod.ts:2602 (element:number), :3015 (element:record_picker) and :4134 (element:repeater). The repeater docblock's "NOT read at the pin" is at :4117.
  • P2 holds. suppliedByDataSource (validate-component-props.ts:169-185) checks the issue path and dataSource.object, never the component type.
  • P3 holds. defaultFilters is z.array(ViewFilterRuleSchema) (:4563), and the defaultSort precedent is objectGridDefaultSortRemoved (conversions/registry.ts:9264).
  • P4 holds. There are zero authors of the eleven keys in examples/**, skills/** and the hand-written content/docs/**. The one element-layer author, the showcase picker (page-variables.page.ts:59), already writes dataSource. The spec, lint and metadata-protocol tests that authored the flat keys as fixtures are re-bound here, and the tree-scoped absence pin found two of them that grep had missed (page.test.ts, i18n-resolver.test.ts). skills/objectstack-ui/rules/pages.md names element:number / element:repeater only as printable blocks and teaches no key, so no skills/** edit is needed and no hand-written docs page needed an edit.

Decisions the reviewer should check

  • Three step-18 narrowings are absorbed, not one. The ruling names object-grid-default-filters-rule-array. element-number-filter-rule-array and element-record-picker-filter-rule-array narrowed the two element flat filter keys this PR retires in the same unreleased major, and their acceptance criteria assert parses this PR refuses (safeParse({ object, aggregate, filter }) succeeds). That is the same-major bookkeeping of the spec-property-retirement playbook (§0) and the measurement's "same-major bookkeeping against … its semantic entries". The record-form guidance they carried is the binding's own entry, element-data-source-and-object-block-filter-rule-array. The CLI's migrate-meta guidance pin drops the three absorbed ids from its REWRITTEN floor. The absorbing entries are not added there, because the list admits only entries rewritten off a tracker id and these two were born without one; the printed-block case holds them with the whole directory.
  • No new public export. The three-element set lives privately in component.zod.ts. The conversion registry and the lint rule each keep a copy, and each copy is pinned against the tombstones by probing ComponentPropsMap. Publishing it would have widened a narrowing-only retirement.
  • Tombstones name @objectstack/spec 17, not 18. check:future-spec-major (ADR-0087, amended 2026-09-13) refuses a tombstone that names the next major. The migrate sentence is --from 17, as with defaultSort.
  • The XParsed aliases of the three element props stay, although their two shapes now coincide. Deleting a published type name would be an export removal of its own.

Acceptance notes (not filed)

  • packages/lint/src/page-walk.ts:135-136 (:137 before the merge of main 76bc1e03a) still resolves a component's object as dataSource.object ?? properties.object ?? page.object for the field-binding rules, and content/docs/deployment/validating-metadata.mdx:157 describes that chain. After this PR the flat object on the three elements is refused by its tombstone, so the fallback only ever reads a refused key (and element:metadata_viewer's metadata owner). This PR does not change it: the file is outside the claim. Carrier: none named; the next card that touches page-walk.ts.
  • The docs gate judges only nodes that carry a properties mapping, so a bare type: element:repeater fence with neither properties nor dataSource is not reported there. The lint rule judges it. This is pre-existing dispatch behaviour.
  • The object-tree read-point record that main re-measured at 20c6d351a (the OBJECT_TREE_FLAT_CONFIG_GUIDANCE docblock header, from chore(objectui): bump the console pin to 20c6d351ad74 (carries objectui#12056) #22592) is kept verbatim, as ordered, with two slips in it. It cites the arm's labelField read as :3986, but at 20c6d351a that read is :3985, and :3986 is fields: treeCfg.fields || effectiveFields,. Its opening sentence still cites ListView.tsx:3913-3932, the range its own re-read moved to 3965-3989. Neither is an asserting quote, so no gate reads them. The corrected titleField paragraph cites :3985. Carrier: the next re-read of that record at a pin bump.
  • os validate refuses the clean repeater on an html-tier page and accepts the retired one. Measured through node packages/cli/bin/run.js validate at this head, on a probe app carrying this repo's sdui.manifest.json:
    • A page whose source is one element:repeater tag carrying only dataSource={{ "object": "probe_task", "limit": 5 }} gets exit 1 with jsx-missing-required-prop (the repeater "is missing required prop "object"").
    • The same tag carrying a flat object="probe_task" instead gets no finding.
    • The cause is objectui's registration at 20c6d351a (components/src/renderers/basic/data-list.tsx:320-326, object required: true), which sdui.manifest.json carries. The manifest also still lists the flat filter / sort / limit inputs, and element:number's flat object / filter.
    • This predates this PR (the binding-only node was legal before it), and the fix site is in objectui. It is reported to the seat as a finding, not fixed here.
  • objectui's own tests at 20c6d351a parse flat object against the spec's element schemas (record-picker-inputs-spec-parity.test.ts, record-picker-empty-text-i18n.test.tsx, types/src/__tests__/element-repeater-data-source-11880.test.ts). They resolve @objectstack/spec ^17 from npm, so nothing reds now. They will need their fixtures re-bound when objectui moves to spec 18. Carrier: objectui's spec-18 upgrade.
  • The committed spec-changes.json and upgrade guide on main lag main's own registries. Since spec(changes): generate the per-major spec-changes section and the upgrade guide at publish; the pull request generates both in memory and renders the diff (#22449 B′, condition 1) #22533, check:spec-changes and check:upgrade-guide generate in memory and no longer compare the committed copies. check-adr-0087-registration still reads the committed spec-changes.json at HEAD as its parser witness, so a PR that removes a registry entry reads "ledger parser drift … widen extractIds()". The real remedy is gen:spec-changes. That is the red this round fixed (below). Regenerating also carried text main had changed in other entries' registries (the objectui pin readings at 20c6d351a, the storage-scope and flow-slot guidance). Carrier: spec(changes): delete the committed spec-changes per-major projection and the upgrade guide copy, with their two merge=os-regen routes, once generation at publish has landed (#22449 B′) #22485, which deletes the committed copies.

This round: main merged, the gate re-measured, the Check Changeset red fixed

  • Merge. origin/main 76bc1e03a was merged by scripts/pm/os-regen-merge.sh (merge commit 1eb154c6f, no rebase, no force-push). The pre-merge tip was d8e0996f4, and the merge base 35ef501e1.
    • One text conflict: packages/spec/dropped-refinements.baseline.json, only its two measured totals. That ledger has no generator by design (its header and scripts/lib/dropped-refinements.ts say why), so the order's "regenerate it" has nothing to run. It was resolved by hand:
      • the branch's 220/680 → 217/677 (three rows retired) and main's 220/680 → 226/688 (six schemas, eight sites) stack at 223 entries / 685 sites;
      • the merged entries list counts exactly that;
      • the spec build's ledger check, which fails on any mismatch, reads "685 refinement site(s) across 223 published schema(s) … all declared".
    • Auto-merged: component.zod.ts, component.test.ts, migrations/registry.ts, i18n-resolver.test.ts and protocol.stored-migration.test.ts. Every line main added to those files between 35ef501e1 and 76bc1e03a is present at this head, with one exception: the object-tree titleField paragraph's ListView.tsx:3927 line, which item 6 replaces on purpose. So main's re-measured pin readings stand verbatim, and the retirement sits on top. One example is the repeater record, which keeps main's data-objectstack/src/index.ts:5048-5059 with this PR's tense change and REMOVED paragraph. gen:migration-registry rewrote registry.ts byte-identically.
    • Step 2 kept the branch's bytes of content/docs/references/ui/component.mdx and authorable-surface/ui.json (branch-only edits). check:generated reads 15/15 current.
  • Regeneration, 675b121bb. Check Changeset was red at 1eb154c6f (check-run 114130272368, "Require an ADR-0087 disposition on a declared-breaking changeset").
    • Reproduced locally: node scripts/check-adr-0087-registration.mjs --base origin/main exit 1, "ledger parser drift: 3 id(s) present in the generated packages/spec/spec-changes.json are NOT found by extractIds()". The three were element-number-filter-rule-array, element-record-picker-filter-rule-array and object-grid-default-filters-rule-array, the entries this PR absorbs. main's committed copy still listed them.
    • gen:spec-changes and gen:upgrade-guide regenerated the committed copies from the merged registries. At 675b121bb the gate exits 0: "1 declared-breaking changeset(s), each carrying an ADR-0087 disposition … registered element-flat-data-binding-retired, object-grid-default-filters-retired".
    • check-changeset-no-major.mjs --base origin/main exits 0 at both heads (pre mode, tag next). No label and no level was changed.
  • Prose, 5faf12295. Item 6 above.
  • Pin citations at 20c6d351a.
    • pnpm --filter @objectstack/spec check:objectui-pin-citations exit 0: 55 asserting citations match .objectui-sha (20c6d351a), and 145 historical ones are recorded and not checked.
    • --verify-anchors (OBJECTUI_ROOT=../objectui, read by git show at the pin) exit 0: 13 anchor content assertions verified against objectui at 20c6d351a.
    • This PR adds or changes no asserting .objectui-sha citation. The objectui anchors it adds are two:
      • data-list.tsx:320-326 (the repeater registration), byte-identical across f0268ad78…20c6d351a, so it holds;
      • ListView.tsx:3985, read at 20c6d351a.
    • No record was re-pointed. The two slips reported above are main's and stay verbatim.
  • Size: 43 files (the 41, plus spec-changes.json and docs/protocol-upgrade-guide.md), +2789 / -1521 = 4310 changed lines. That is over the 3000-line human-merge line (chore(governance): the human-merge line threshold is 3,000 changed lines (HUMAN_MERGE_LINE_THRESHOLD), per the maintainer's 2026-10-09 ruling #22490).

Tests and gates

All readings are at head 5faf12295, and every exit code was captured before any pipe. Heavy runs went through scripts/pm/os-verify-lock.sh on a shared box.

  • @objectstack/spec, both tiers in full (vitest run --project local --project repo --maxWorkers=4): 694 files, 20015 passed, 1 todo, 0 failed. Lock verdict: command-exit 0, held 16m44s. pnpm --filter @objectstack/spec typecheck: exit 0.
  • @objectstack/lint, full (vitest run --maxWorkers=4): 134 files, 6128 passed. typecheck: exit 0.
  • @objectstack/cli:
    • the unit tier in full (vitest run --project unit): 277 files, 4105 passed;
    • test/migrate-meta-engine-guidance.test.ts, an integration-tier file this PR edits (--project integration): 3 passed;
    • typecheck: exit 0;
    • the rest of the integration tier is left to CI.
  • @objectstack/metadata-protocol src/protocol.stored-migration.test.ts: 40 passed. examples/app-showcase test/my-work-visibility.test.ts: 7 passed.
  • The builds behind these: the spec build (exit 0), turbo run build --filter='@objectstack/cli^...' (58/58 tasks), and the CLI build. A locked full build followed (72/72 tasks).
  • Gates. node scripts/pm/dispatch-gates.mjs --commands derived 120 at 5faf12295.
    • All 120 were run, and all 120 exit 0.
    • On the first pass, check:skill-examples and check:dual-build-cjs-loads exited 3 (PREREQUISITE NOT MET: no dist/ for client-react and eight others). After the full build both exit 0.
    • --ran: "120 run, 0 NOT-MEASURED".
    • check:pm-dispatch-gates: 1825 cases pass (fast tier; 6 slow sections deferred).
  • CI at 5faf12295, read once when the report was written (not awaited): all 35 check runs completed, 33 success and 2 skipped (Console Pin Gate, Packed-tarball smoke opt-in), none red. Check Changeset is green again.

维护者速读(草稿)

改了什么

  • 页面里的三个元素组件,不再接受直接写在 properties 里的数据绑定键:
    • 记录选择器 element:record_picker:object / filter / sort / limit;
    • 数字 element:number:object / filter;
    • 重复列表 element:repeater:object / filter / sort / limit。
  • 作者改为写在组件节点的 dataSource 上,即 dataSource.object、dataSource.filter、dataSource.sort、dataSource.limit。repeater 原来的别名(objectName、filters、where、orderBy、sortBy、top、pageSize)也一并指向 dataSource。
  • 对象表格 object-grid 的 defaultFilters 不再接受,规则改写到 filter。
  • 旧写法在校验和保存时会被拒绝,错误信息里直接给出新写法。os validate 对缺少 dataSource.object 的这三个元素给出警告。
  • 已有元数据可以用 os migrate meta --from 17 自动改写。改写不了的情况会留一条 TODO 提示作者手工处理:两种写法取值不同,或者 dataSource 指向了一个已保存的视图。
  • Studio 的页面设计器在新的控制台 pin(.objectui-sha = 20c6d351a)上,已经按新形状写这三个元素。repeater 的“对象”和“条数”两栏写入 dataSource,作者在 Studio 里拖出来的组件不会被拒绝。

为什么改

  • 同一个查询原来有两个入口(properties 和 dataSource),三个组件各自用不同规则合并两边,作者写下的值不一定生效。
  • repeater 还有一个陷阱:只写 dataSource 时,os validate 显示通过,页面却显示“No records”。
  • 裁决 A-narrow(6051498447)定为:只保留 dataSource 一个入口,先让 objectui 读写新形状,再在 spec 里收回旧键。这一步现在已经具备条件。

风险与代价(含回滚)

  • 破坏性变更。 属于 v18 大版本:@objectstack/spec 与 @objectstack/lint 带 major changeset。没有跑迁移的旧元数据会在校验时报错。
  • 宿主 pin 规则。 同时 pin 两个仓库的宿主,要先移动 objectui 的 pin,越过 objectui#11880,才能读迁移后的形状;之后才能把 objectstack 的 pin 移过本次退役。Studio 设计器按新形状写 repeater,要从 objectui#12056 起;当前控制台 pin 20c6d351a 两者都已包含。
  • 已知遗留(不在本 PR 内)。
    • html 类型页面里,os validate 仍要求 element:repeater 标签写平铺的 object,并拒绝只写 dataSource 的写法。原因在 objectui 的组件注册清单,已作为发现交给席位处理。
    • objectui 自己的部分测试在升级到 spec 18 时,需要改用新写法。
  • 体量。 4310 行变更,超过 3000 行人工合并线。
  • 回滚。 直接 revert 本 PR 的合并提交即可。已经迁移成 dataSource / filter 的元数据,在旧版本里本来就是合法写法,回滚后不需要再迁移回去。

席位意见

你要做的(一个动作)

审阅并亲手合并 PR #22421。


Generated by Claude Code

claude added 11 commits October 9, 2026 01:16
…t-grid defaultFilters (#11509)

Tombstones, the two protocol-18 conversions, the registry entries and
the absorbed step-18 narrowings. Tests and generated artifacts follow.

Claude-Session: https://claude.ai/code/session_01DhTqaEHqPVSVnAkjG3jywn
Co-authored-by: Claude <noreply@anthropic.com>
…e three data-source-bound elements

Claude-Session: https://claude.ai/code/session_01DhTqaEHqPVSVnAkjG3jywn
Co-authored-by: Claude <noreply@anthropic.com>
…e retirement moves

Claude-Session: https://claude.ai/code/session_01DhTqaEHqPVSVnAkjG3jywn
Co-authored-by: Claude <noreply@anthropic.com>
…ter pins follow the binding

Claude-Session: https://claude.ai/code/session_01DhTqaEHqPVSVnAkjG3jywn
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/lint, @objectstack/spec, touching 54 documentable anchor(s). ⚠️ 15 changed file(s) yielded no anchor (packages/spec/authorable-surface/ui.json, packages/spec/dropped-refinements.baseline.json, packages/spec/spec-changes.json, …), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

50 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json 76bc1e03a3237150934a35370406a5ddd7cde4fb.

⛔ 9 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • 15 changed file(s) yielded no anchor (packages/spec/authorable-surface/ui.json, packages/spec/dropped-refinements.baseline.json, packages/spec/spec-changes.json, …) — pages documenting those are invisible to this run
  • 2 anchor(s) matched too much of the corpus to be a work list: created_at (literal, 38 pages), objectName (literal, 38 pages)
  • 16 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 139 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 76bc1e03a3237150934a35370406a5ddd7cde4fb → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 714d8f2c2d2291474fa96f3adbbad797b1b86686 — the merge of head 5faf12295faaf4d53d2d9d797e90470685084e67 into base 76bc1e03a3237150934a35370406a5ddd7cde4fb, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 714d8f2c2d2291474fa96f3adbbad797b1b86686 && git checkout 714d8f2c2d2291474fa96f3adbbad797b1b86686
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 76bc1e03a3237150934a35370406a5ddd7cde4fb 5faf12295faaf4d53d2d9d797e90470685084e67 && git checkout -B drift-repro 76bc1e03a3237150934a35370406a5ddd7cde4fb && git merge --no-ff 5faf12295faaf4d53d2d9d797e90470685084e67

node scripts/docs-audit/affected-docs.mjs --json 76bc1e03a3237150934a35370406a5ddd7cde4fb

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 76bc1e03a3237150934a35370406a5ddd7cde4fb → pass the list as
args.docs, on the commit named under Which tree this was computed on.

claude added 4 commits October 9, 2026 10:59
…ement-binding-retirement

# Conflicts:
#	packages/spec/dropped-refinements.baseline.json
#	packages/spec/src/ui/component.zod.ts
… first and keeps its flat keys as a fallback

Claude-Session: https://claude.ai/code/session_01DhTqaEHqPVSVnAkjG3jywn
Co-authored-by: Claude <noreply@anthropic.com>
This was referenced Oct 9, 2026
…ement-binding-retirement

Merges origin/main 76bc1e0 through scripts/pm/os-regen-merge.sh.

One text conflict, in the hand-edited ledger
packages/spec/dropped-refinements.baseline.json (it has no generator by
design): only the two `measured` totals collided. The branch retires three
rows (220/680 -> 217/677) and main adds six schemas and eight sites
(220/680 -> 226/688), so both stack at 223 entries / 685 sites, which is
what the merged `entries` list itself counts. The build's ledger check is
the authority and is re-run on this tree.

Claude-Session: https://claude.ai/code/session_01DhTqaEHqPVSVnAkjG3jywn
Co-authored-by: Claude <noreply@anthropic.com>
…e merged tree

Step 4 of the os-regen merge of origin/main 76bc1e0, through the
packages' own generators (`gen:spec-changes`, `gen:upgrade-guide`;
`gen:migration-registry` rewrote registry.ts byte-identically).

The committed spec-changes.json main brought still listed the three
step-18 entries this branch absorbs (element-number-filter-rule-array,
element-record-picker-filter-rule-array,
object-grid-default-filters-rule-array) and lacked this branch's two D3
entries. `check:spec-changes` no longer compares the committed copy (it
generates in memory), so it read green, while
`check-adr-0087-registration` reads the committed copy at HEAD as its
parser witness and refused with "ledger parser drift". Regenerating the
committed copies from the merged registry clears it. The copies also
pick up registry text main changed without regenerating them (the
objectui pin readings at 20c6d351a, the storage-scope and flow-slot
guidance).

Claude-Session: https://claude.ai/code/session_01DhTqaEHqPVSVnAkjG3jywn
Co-authored-by: Claude <noreply@anthropic.com>
…n again

At objectui 20c6d351a nothing reads a tree `titleField`: the renderer's
`getTreeConfig` rung went on objectui#8841, and the `case 'tree'` arm's
flatten rung went on objectui#6152 round 14 (objectui `3fd862510`), so
`ListView.tsx:3985` now reads `treeCfg.labelField || 'name'` and emits no
`titleField`. The OBJECT_TREE_FLAT_CONFIG_GUIDANCE docblock and the last
sentence of its prescription said the key was "only ever read as that
key's last fallback", a read that no longer exists.

The prose now says why the key stays in the set (for its prescription,
not for a read), and the prescription still names `tree.labelField`. The
flat key set and every schema shape are unchanged; keeping or dropping
`titleField` from the set is a schema decision outside this change. The
in-test note beside the key-set pin gains the same dated reading.

Claude-Session: https://claude.ai/code/session_01DhTqaEHqPVSVnAkjG3jywn
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

维护者速读

domain:spec seat 2 (#18549) · os-sales · session session_01DhTqaEHqPVSVnAkjG3jywn · 2026-10-10T05:21Z。This is the final version of the PR body's draft, corrected against the diff the seat read at 5faf12295 (ACCEPT 6094161139 on #11509).

改了什么

  • 页面里的三个元素组件,不再接受直接写在 properties 里的数据绑定键:
    • 记录选择器 element:record_picker:object / filter / sort / limit;
    • 数字 element:number:object / filter;
    • 重复列表 element:repeater:object / filter / sort / limit。
  • 作者改为写在组件节点的 dataSource 上,即 dataSource.object、dataSource.filter、dataSource.sort、dataSource.limit。repeater 原来的别名(objectName、filters、where、orderBy、sortBy、top、pageSize)也一并指向 dataSource。
  • 对象表格 object-grid 的 defaultFilters 不再接受,规则改写到 filter。
  • 旧写法在校验和保存时会被拒绝,错误信息里直接给出新写法。os validate 对缺少 dataSource.object 的这三个元素给出警告。
  • 已有元数据可以用 os migrate meta --from 17 自动改写。改写不了的情况会留一条 TODO 提示作者手工处理:两种写法取值不同,或者 dataSource 指向了一个已保存的视图。
  • Studio 的页面设计器在新的控制台 pin(.objectui-sha = 20c6d351a)上,已经按新形状写这三个元素。repeater 的“对象”和“条数”两栏写入 dataSource,作者在 Studio 里拖出来的组件不会被拒绝。

为什么改

  • 同一个查询原来有两个入口(properties 和 dataSource),三个组件各自用不同规则合并两边,作者写下的值不一定生效。
  • repeater 还有一个陷阱:只写 dataSource 时,os validate 显示通过,页面却显示“No records”。
  • 裁决 A-narrow(6051498447)定为:只保留 dataSource 一个入口,先让 objectui 读写新形状,再在 spec 里收回旧键。这一步现在已经具备条件。

风险与代价(含回滚)

  • 破坏性变更。 属于 v18 大版本:@objectstack/spec 与 @objectstack/lint 带 major changeset。没有跑迁移的旧元数据会在校验时报错。
  • 宿主 pin 规则。 同时 pin 两个仓库的宿主,要先移动 objectui 的 pin,越过 objectui#11880,才能读迁移后的形状;之后才能把 objectstack 的 pin 移过本次退役。Studio 设计器按新形状写 repeater,要从 objectui#12056 起;当前控制台 pin 20c6d351a 两者都已包含。
  • 已知遗留(不在本 PR 内)。
  • 体量。 4310 行变更,超过 3000 行人工合并线。
  • 回滚。 直接 revert 本 PR 的合并提交即可。已经迁移成 dataSource / filter 的元数据,在旧版本里本来就是合法写法,回滚后不需要再迁移回去。

席位意见

你要做的(一个动作)

审阅并亲手合并 PR #22421。


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 5faf12295faaf4d53d2d9d797e90470685084e67
Local-runs: none

Read: card #11509's body and all 23 comments (the ruling 6051498447 A-narrow with its four sub-questions, the claim 6072058120, the gate move 6080485027, the seat's fix-round acceptance 6084026144, the devx seat's unlock and offer 6093574958, the seat's last-round order 6093686803, the os-dev-report 6094137743, the seat's ACCEPT 6094161139); PR #22421's body, its 43-file list and the net diff against main at the merge base 76bc1e03a (43 files, +2789 / -1521, equal to the file list), read with git diff on the fetched head and never built; the check-runs on the head. The governing texts (ADR-0087 D2/D3, ADR-0122, Prime Directive #12, the retirement kit of spec-property-retirement) were read on origin/main. Rendered by the director seat on the maintainer's word in this seat's session, verbatim 「22421 帮我契约复审」, at 2026-10-10T05:34Z. ⛔ Classes, positions and functions only.

Form. Draft; head repo equals base repo; line 1 Fixes #11509, line 2 Clause-②: no (narrowing: …); the PR assignee os-sales equals the card's. Not governed: no path is a GOVERNED_SURFACES row (content/docs/references/ui/component.mdx and docs/protocol-upgrade-guide.md are generated references, not ADRs or skills). 4,310 changed lines, over the 3,000 human-merge line: this PR lands by the maintainer's own merge after an authorized approval, never by a seat (the review requests to os-zhuang and hotlong are on it). origin/main has moved past the merge base; GitHub reports the PR clean.

Gates on the head. 46 check-runs, none still running: 39 success, 7 rostered skips (Auto Label ×2, Check PR Size ×2, Console Pin Gate, Packed-tarball smoke ×2), none red. Among the successes: Check Changeset (the ADR-0087 disposition read from the regenerated spec-changes.json), TypeScript Type Check (which carries check:api-surface, check:authorable-surface, check:future-spec-major and the generated-artifact gates), Test Core and its shards, Dogfood Regression Gate, Build Core, Lint & Repo Gates, Governed Surface Queue Guard, the card and single-writer checks. Those conclusions are the gate verdicts; nothing was built, run or re-run here.

① Derived judgments

Each row is an accept-set or public-surface change the diff implies, read off the schema, the lint rule, the conversion registry and the ledgers against their main versions.

  1. Ten element-layer flat binding keys retire as tombstones — RIGHT. element:record_picker object / filter / sort / limit, element:number object / filter, element:repeater object / filter / sort / limit are retiredKey(...) in component.zod.ts, each refused at its key with a prescription generated from the element and the key (elementFlatBindingRetired), so the ten strings differ only where the elements' old precedence rules differed (picker: the binding won, delete the flat copy; number: the two filters AND-combined, append; repeater: the list read the flat keys alone, decide). Input type never is pinned (element-flat-binding-retirement.test.ts, "fails tsc at the authoring site"). The set is module-private (RETIRED_ELEMENT_FLAT_BINDING_KEYS), as the ruling's narrowing requires. This is sub-question-free A-narrow as ruled.
  2. object-grid.defaultFilters retires — RIGHT (ruling sub-question 1). The tombstone's prescription says rename to filter where filter is empty and delete where filter has rules, and the conversion object-grid-default-filters-removed carries exactly that (empty means absent, null, [] or {}; a bare-string filter is a TODO, never overwritten). The deleted pin file component-object-grid-default-filters.pin.test.ts had said the key is "narrowed, NOT retired" pending "its own ruling"; that ruling is 6051498447, and the replacement pins live in the new test's object-grid.defaultFilters block, with the filter control.
  3. The repeater's seven alias spellings leave the accept set too — RIGHT, with one note under ③. objectName, filters, where, orderBy, sortBy, top, pageSize were aliases of the four flat keys and are now guidance entries pointing at the binding (elementRepeaterBindingGuidance), as alias-integrity.test.ts requires once the targets are tombstones. The changeset names them in prose.
  4. The object-* block layer and the relationship-scoped blocks are unchanged — RIGHT. The only other component.zod.ts edit is the OBJECT_TREE_FLAT_CONFIG_GUIDANCE prose on titleField (the devx seat's offer item 2): the key set and every shape stay, and the prescription still names tree.labelField.
  5. No new public export — RIGHT, in both packages. component.zod.ts adds or removes no export line; the three ...PropsParsed type aliases stay (deleting a published type name would be an export removal of its own, as the body says); no api-surface/ shard changes, and the api-surface gate is green. In @objectstack/lint the module validate-component-props.ts gains export const DATA_SOURCE_BOUND_ELEMENT_TYPES, but src/index.ts (unchanged in this PR) re-exports an explicit name list without it, and the package exports map addresses only ., ./runtime and ./rule-explanations, so the constant is not reachable from the published surface; a legacy deep import is outside the contract.
  6. The lint waiver becomes a refusal on the binding — RIGHT (ruling sub-question 3). suppliedByDataSource is deleted; a node of one of the three types with no dataSource.object is a component-props-invalid finding at path.dataSource.object, warning tier, the same rule id as every other value verdict; a flat key beside it is refused by its tombstone with the prescription. The docs gate's twin (check-yaml-examples.ts) turns with it, and both copies of the three-type set are pinned against ComponentPropsMap's tombstones so none can drift alone. The repeater trap of sub-question 4 is closed from both sides: the binding-only repeater is the clean shape (zero findings; objectui reads it since objectui#11880), and a flat object is refused twice.
  7. Conversions — RIGHT. element-flat-data-binding-to-data-source and object-grid-default-filters-removed are registered at order 35.5, before page-component-filter-record-to-rule-array at 36, both retiredFromLoadPath: true (an author is refused at the parse; stored rows and os migrate meta --from 17 replay them). page-component-filter-record-to-rule-array drops the two absorbed element doors and the defaultFilters door from its reach, so a moved record-form filter is converted once, at its new door. The three TODO classes are the ones the ruling's old rules cannot decide mechanically, and they are reported, not guessed.
  8. The ADR-0087 ledger — RIGHT. Eleven RETIRED_KEYS_BY_MAJOR[18] entries (one file each under migrations/entries/retired-keys/), two D3 semantic entries with step-18 rationale fragments, each naming its D2 conversion; the three absorbed entries (element-number-filter-rule-array, element-record-picker-filter-rule-array, object-grid-default-filters-rule-array) were all 18.* entries of an unreleased major (.changeset/pre.json is in next pre mode on main, and cloud is pinned on the v17 line), so folding them is same-major bookkeeping, not a rewrite of history. migrations/registry.ts is regenerated (gen:migration-registry, byte-identical per the report), and spec-changes.json and the upgrade guide are regenerated from the merged registries, which is what turned Check Changeset green at 675b121bb.
  9. Ledgers and generated artifacts — RIGHT. authorable-surface/ui.json carries the eleven [RETIRED] rows; dropped-refinements.baseline.json loses the three rows whose only dropped refinement was a retired filter (shrink-only, shrinking), and the hand-resolved merge totals 223 / 685 are the ones the spec build's ledger check accepts (green on the head).
  10. Tests — RIGHT. The deleted component.test.ts blocks pinned the flat filter orthography and the flat sort / limit shorthands of keys that no longer exist; their replacements judge the live filter doors and the binding. The new 745-line pin file covers tombstones, prescriptions, the never input type, the binding, both conversions case by case, idempotence, the jurisdiction (authoring refused, stored rows and os migrate replayed), the ledger rows, and a tree-scoped absence walk with an anti-vacuity control.

② Semver level

.changeset/11509-element-flat-binding-retired.md: @objectstack/spec: major, @objectstack/lint: major; Clause-②: no (narrowing: …) on the PR's line 2 and in the changeset, consistent with ① (every change narrows or is internal; nothing widens the published surface). Narrowing is BREAKING, so major is the floor, met; the release is 18.0.0-next.* under the open pre mode. The changeset carries the FROM → TO table, the one-line fix, the ADR-0087 marker registered element-flat-data-binding-retired, object-grid-default-filters-retired, the consumer pin rule (objectui past objectui#11880 before objectstack past this release) and the measured who-is-affected. @objectstack/lint at major is right: its component-props rule refuses what it waived.

③ Boundary flags

  • Dev decisions offered for review (4): the absorption of three step-18 entries — RIGHT (①.8); no new public export — RIGHT, verified in both packages (①.5); tombstones name @objectstack/spec 17 — RIGHT (check:future-spec-major refuses the next major; the migrate sentence is --from 17, the defaultSort precedent); the ...PropsParsed aliases kept — RIGHT.
  • open_questions[0] (the PR body not written by the last run) — answered A by the seat (6094161139): the body on the head carries the met gate, the readings and the 速读 draft, and the seat posted the final 速读 (6094165932).
  • Acceptance notes (6), each with a carrier: page-walk.ts's dataSource.object ?? properties.object ?? page.object chain now falls through a refused key (carrier: the next card touching that file) — accepted, outside this claim; the docs gate judges only nodes with a properties mapping (pre-existing dispatch) — accepted; the two non-asserting citation slips from chore(objectui): bump the console pin to 20c6d351ad74 (carries objectui#12056) #22592 (carrier: the next pin bump's re-read) — accepted; the html-page door refusing a binding-only element:repeater because the sdui manifest registers object as required — filed by the seat as objectui#12085 and named in the 速读's risk section — accepted; objectui's own tests parsing flat object against spec 17 (carrier: objectui's spec-18 upgrade) — accepted; check-adr-0087-registration's wrong remedy text when the committed spec-changes.json lags (carrier: spec(changes): delete the committed spec-changes per-major projection and the upgrade guide copy, with their two merge=os-regen routes, once generation at publish has landed (#22449 B′) #22485) — accepted.
  • One flag the dev did not raise, recorded here, not blocking. The seven repeater alias spellings (①.3) leave the accept set with a prescription at the parse, but the conversion element-flat-data-binding-to-data-source moves only the four canonical keys, so a stored repeater authored with objectName, where, top or the other spellings is refused loudly at rehydration with the prescription and is not listed by os migrate meta --from 17. That is the loud direction Prime Directive Add comprehensive test suite for Zod schema validation #12 asks for, the in-repo census found no author of any of them, and the changeset names the spellings, so it does not change the verdict. Carrier: the domain:spec seat, as an Acceptance note on [Direction · v18] Retire the flat object/filter data-binding family — dataSource as the single data-binding door (re-anchor of the deleted #6590 tracker) #11509, to decide whether a one-sentence upgrade-guide addition or a conversion extension is worth its code.

Breaker readings: none. Nothing widens; no governed path; no release act.

Implemented-by: claude/issue-11509-element-binding-retirement
Reviewed-by: session_019fWAt2renophxLVg5aJXMH

VERDICT: PASS


Generated by Claude Code

@os-zhuang
os-zhuang marked this pull request as ready for review October 10, 2026 05:47
@os-zhuang
os-zhuang enabled auto-merge October 10, 2026 05:47

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

3 participants