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

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

objectstack-fleet[bot] wants to merge 17 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 (claim 6072058120, ruling sub-question 2): .objectui-sha must cover objectui 5bc55c0c5a1e (the merge of objectui PR #12010) before this lands. The pin bump is its own card, #22385 (claimed by the devx seat); this PR does not move .objectui-sha. At head 756fa0b33, .objectui-sha is f0268ad78485, and REST compare 5bc55c0c5a1e...f0268ad78485 reads ahead by 8, behind by 0: the pin covers 5bc55c0c5a1e. #22385 landed. This branch merged origin/main 3ca71b6e0 with os-regen-merge.sh, kept the re-measured repeater citation verbatim, and added the retirement on top.

Held again (seat 6080485027): the gate moves to #22476. At that pin, Studio's page-block inspector still writes the repeater's flat properties.object and properties.limit (block-config.ts:338–:345), the keys this PR retires. The ruling's order is that the designer writes dataSource first. The fix is objectstack-ai/objectui#12056, and #22476 bumps the pin past it. This PR stays a held draft until .objectui-sha covers that fix; then it takes a merge round onto the then-current main and a contract review.

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.

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:137 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.

Tests and gates

All readings below are at head d8e0996f4: c1d688804 (the CLI pin fix), then origin/main 35ef501e1 merged by scripts/pm/os-regen-merge.sh, with no conflict and no generated path to regenerate (check:generated 15/15 current). Taken on a shared box under scripts/pm/os-verify-lock.sh.

  • @objectstack/cli as Test Core runs it (pnpm turbo run test test:repo --filter=@objectstack/cli, VITEST_MAX_WORKERS from vitest-worker-cap.mjs): 373 files, 4964 passed, 2 skipped, both tiers. test/migrate-meta-engine-guidance.test.ts: 3 passed. At 756fa0b33 it read 1 failed / 2 passed, with "family lost element-number-filter-rule-array".
  • @objectstack/spec unit tiers in full (vitest run --project local --project repo): 684 files, 19731 passed, 1 todo, 0 failed. pnpm typecheck: exit 0.
  • (at 756fa0b33; CI Test Core 1/6 to 6/6 is green at d8e0996f4) @objectstack/lint full (vitest run): 129 files, 5912 passed. pnpm typecheck: exit 0.
  • (at 756fa0b33; CI Test Core 1/6 to 6/6 is green at d8e0996f4) @objectstack/metadata-protocol src/protocol.stored-migration.test.ts: 40 passed. pnpm typecheck: exit 0.
  • (at 756fa0b33; CI Test Core 1/6 to 6/6 is green at d8e0996f4) examples/app-showcase test/my-work-visibility.test.ts: 7 passed. pnpm typecheck: exit 0.
  • Ablation of the lint refusal (round 1; the guard is byte-unchanged since, so it was not re-run) (one-off, scripts/ablation-replace.mjs, run from the committed head): the guard was mutated to if (false && …), the mutation was proven on disk (anchor 1 → 0, blob 5c1af3370366 → 30bf2ab540c7), and validate-component-props.test.ts went to 9 failed / 56 passed. Restored: the blob equals HEAD 5c1af3370366 and git diff HEAD is empty. The lint subject is imported from source, so no build leg was involved.
  • The cross-package read is a built dist (the lint test reads @objectstack/spec/ui). It is proven fresh by the pin that derives the three element types from ComponentPropsMap: against a pre-retirement dist that set is empty, and the pin is red.
  • Gates: dispatch-gates --commands derived 120 at this head (last round's 119 plus check:cli-test-child-env), and all 120 were run. --ran: "120 run, 0 NOT-MEASURED". All 120 exit 0, after a locked full build. check:objectui-pin-citations: 55 asserting citations match f0268ad78, and its 13 anchor content assertions are verified against objectui f0268ad78, fetched in isolation. check:pm-dispatch-gates: 2011 cases pass.
  • Size: 4089 changed lines (+2669 / -1420, 41 files) by the dispatch-gates count at d8e0996f4. That is over the 3000-line human-merge threshold main set in 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 (2026-10-09), so this PR lands by a human merge.
  • CI at d8e0996f4: all 35 check runs completed, 33 success and 2 skipped (Console Pin Gate, Packed-tarball smoke opt-in), none red.

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). ⚠️ 14 changed file(s) yielded no anchor (packages/spec/authorable-surface/ui.json, packages/spec/dropped-refinements.baseline.json, packages/spec/src/migrations/entries/retired-keys/18.ui__ElementNumberProps__filter.ts, …), 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 35ef501e1301a2e992f4f785a9ef469ce79fc086.

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

What this run could not see
  • 14 changed file(s) yielded no anchor (packages/spec/authorable-surface/ui.json, packages/spec/dropped-refinements.baseline.json, packages/spec/src/migrations/entries/retired-keys/18.ui__ElementNumberProps__filter.ts, …) — 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)
  • 15 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 35ef501e1301a2e992f4f785a9ef469ce79fc086 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 864718bb7754351336c85bc26812058890dafc90 — the merge of head d8e0996f4f95b99951d65548657aa323c98d35eb into base 35ef501e1301a2e992f4f785a9ef469ce79fc086, 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 864718bb7754351336c85bc26812058890dafc90 && git checkout 864718bb7754351336c85bc26812058890dafc90
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 35ef501e1301a2e992f4f785a9ef469ce79fc086 d8e0996f4f95b99951d65548657aa323c98d35eb && git checkout -B drift-repro 35ef501e1301a2e992f4f785a9ef469ce79fc086 && git merge --no-ff d8e0996f4f95b99951d65548657aa323c98d35eb

node scripts/docs-audit/affected-docs.mjs --json 35ef501e1301a2e992f4f785a9ef469ce79fc086

⚠️ 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 35ef501e1301a2e992f4f785a9ef469ce79fc086 → 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>
claude added 2 commits October 9, 2026 14:18
…18 entries this retirement absorbs

The REWRITTEN floor in migrate-meta-engine-guidance.test.ts still named
element-number-filter-rule-array, element-record-picker-filter-rule-array
and object-grid-default-filters-rule-array. The element flat binding
retirement absorbs all three into its own D3 entries, so none of them is in
MIGRATIONS_BY_MAJOR any more and the floor case read "family lost
element-number-filter-rule-array".

The list's own rule admits only entries rewritten when their family was
brought to the no-tracker-id line; an entry born without a tracker id needs
no row, because the whole directory is held by the printed-block case. The
two absorbing entries (element-flat-data-binding-retired,
object-grid-default-filters-retired) were born that way, so they are not
added. The assertion is unchanged.

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

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

2 participants