Skip to content

fix(lint,metadata-protocol,metadata-core)!: os lint and the react-page props check refuse the retired list-view filterableFields by name, and its residue readers go - #24878

Merged
objectstack-fleet[bot] merged 7 commits into
mainfrom
claude/issue-24728-filterablefields-residue
Oct 20, 2026
Merged

objectstack-fleet[bot] merged 7 commits into
mainfrom
claude/issue-24728-filterablefields-residue

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Closes #24728
Clause-②: yes (narrowing)

What this does

@objectstack/spec 18 retired the list view's filterableFields with a retiredKey() tombstone (#24640), so os validate and os build refuse it at the parse. os lint never parses, and a react page's source is parsed against no list-view schema, so the lint package still treated the key as a live field position. This PR removes every residue reader on the card's list, and turns the two that judge authoring input into a refusal by name that carries the tombstone's prescription.

os lint (list views). validate-list-view-field-refs.ts no longer walks filterableFields (it is gone from the severity map POSITIONS and the dotted-path map DOTTED_AXIS). Instead every list-view rung that still carries the key reports a new rule, list-view-key-retired (error, at ...filterableFields), whatever the key names and whether or not the object binding resolves. Before, on a JSON/YAML/raw source, a valid name passed os lint silently and a stale one was reported as list-view-field-unknown. The prescription is READ from ListViewSchema's tombstone description ([REMOVED] ...), never retyped: the message carries its verdict head, the hint its "Delete the key" sentence, cut by the existing retiredKeyRemedy helper, the way the permission-residue rule already does it. The rule id is exported from the package root as LIST_VIEW_KEY_RETIRED (the package's own rule-id-barrel-exports test requires it). The keys reported are one frozen list, LIST_VIEW_RETIRED_KEYS_REPORTED = ['filterableFields']: the retired keys this rule used to read as field positions.

React-page props (done first, per triage). REACT_FIELD_SPECS.ListView.fields no longer lists filterableFields. The ListView block now carries retiredSchemaProps, built from the same list and the same tombstone reader. Writing the prop is refused as react-prop-retired (error) with "is retired and no prop replaces it" plus the tombstone's verdict head and remedy. The old "covers the legacy filterableFields shorthand" test is replaced: a valid name and a stale name both get exactly one react-prop-retired and no page-field-unknown, the hint equals the remedy read from the tombstone, and a userFilters lit control is not refused.

The other residue readers.

  • validate-field-consumers.ts: filterableFields leaves BEHAVIOUR_SEGMENTS.
  • rule-explanations.ts: os explain list-view-field-unknown and list-view-field-dotted no longer name it as a checked position.
  • metadata-protocol/src/metadata-diagnostics.ts: the dead walk is deleted, folded into the tabs retirement comment beside it.
  • metadata-core/src/object-schema-fls-references.ts: LIST_VIEW_NAME_LISTS.filterableFields is deleted. A body that still carries the key now goes the unclassified way and fails closed. Measured with a one-off probe against applyObjectSchemaMask (caller may read id and title): a list view whose filterableFields names the denied secret_score is dropped whole, and one naming only title is served unchanged. Output: {"b":{"label":"B","columns":["title"],"filterableFields":["title"]}}. No denied name leaks.
  • Fixtures: objectql/src/metadata-diagnostics.test.ts (the array-shaped-fields case now uses userFilters.fields) and the lint field-refs tests (FULL_LIST_VIEW, the per-position case table, the dotted filter-position case).

The ratchet. RESIDUE in packages/spec/src/ui/view-list-filterable-fields-retirement.test.ts is now empty. All four entries left in this PR. The new pin file packages/lint/src/list-view-filterable-fields-retired.test.ts authors the key as a key on purpose, so it joins EXCLUDED by name with its reason, beside the spec pin it twins. The walk's offender list is [] and its anti-vacuity floors still hold (38/38 tests pass).

Classification (changeset .changeset/24728-filterable-fields-residue.md)

@objectstack/lint minor, @objectstack/metadata-protocol patch, @objectstack/metadata-core patch. BREAKING (an authoring narrowing), shipped minor under the launch-window convention.

  • Narrowing. The react-page props check is a member of the reference-integrity suite with its default runtime types, so it runs on os validate, os build and os lint only, not on the page save door. A react page writing the ListView element's filterableFields prop used to pass all three commands whenever its names resolved. It is now refused. On list views, os lint now fails a raw source that os validate already refused.
  • Widening of the public surface. A new exported rule-id constant, LIST_VIEW_KEY_RETIRED.
  • So the line is Clause-②: yes (narrowing). That follows the precedent of the two list-view door refusals that added an exported rule id (list-view-object-outside-closure, list-view-object-unknown). ⚠️ The claim's line read Clause-②: no. It was written before the rule id existed, and this PR carries the corrected line instead of copying it.
  • ADR-0087 disposition: not-required (already-registered view-list-filterable-fields-removed, list-view-filterable-fields-retired). The key's tombstone, D2 conversion and D3 entry already exist (from spec(ui): ListViewSchema.filterableFields is described as the userFilters.fields shorthand, but its one renderer reads it as the Filter panel whitelist; retire or redefine (objectui#13047) #24640, same unreleased major), and this PR adds no key, conversion or stored shape. The changeset carries the FROM → TO table and the one-line fix.

Verification (final head 7077ebc775, the merge of origin/main 81120caac8)

  • pnpm --filter @objectstack/lint test: 151 files / 6994 tests passed. typecheck (tsc + check:test-typecheck): pass.
  • On 5c3a09b412 (before the merge, which touched neither package): pnpm --filter @objectstack/metadata-core test: 18 files / 780 passed, plus typecheck. pnpm --filter @objectstack/metadata-protocol test: 251 files / 20828 passed (3 files / 19 tests skipped), plus typecheck. pnpm --filter @objectstack/objectql typecheck: pass, and src/metadata-diagnostics.test.ts: 10/10.
  • pnpm --filter @objectstack/spec exec vitest run src/ui/view-list-filterable-fields-retirement.test.ts: 38/38. pnpm --filter @objectstack/spec check:generated: "All 14 generated artifacts are up to date" (spec built first).
  • Gate families from node scripts/pm/dispatch-gates.mjs --commands (92 commands): all 92 run on 7077ebc775, each exit 0, and --ran reconciliation answered "92 derived, 92 run, 0 NOT-MEASURED, 0 UNRUN". Among them: check:adr-0087-registration ("ADR-0087 exemption (already-registered)"), check-changeset-no-major, check-empty-changeset, check:nul-bytes and check:cross-package-test-inputs.
  • Reach through the os lint registry: runAuthoringRules('lint', { normalized }) on an unparsed stack whose list view carries filterableFields: ['status'] returns exactly one list-view-key-retired error at views[0].list.filterableFields (pinned in the new test file). A spawned os lint CLI run was NOT MEASURED: the CLI package was not built in this worktree.

Acceptance notes


Generated by Claude Code

@github-actions

github-actions Bot commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 3 package(s): @objectstack/lint, @objectstack/metadata-core, @objectstack/metadata-protocol, touching 22 documentable anchor(s). ⚠️ 1 changed file(s) yielded no anchor (packages/lint/src/index.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

8 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/concepts/architecture.mdx (via ListView (symbol, a field of const object REACT_FIELD_SPECS), userFilters (literal, a string literal in BEHAVIOUR_SEGMENTS))
  • content/docs/deployment/validating-metadata.mdx (via ListView (symbol, a field of const object REACT_FIELD_SPECS), userFilters (literal, a string literal in BEHAVIOUR_SEGMENTS))
  • content/docs/getting-started/quick-reference.mdx (via ListView (symbol, a field of const object REACT_FIELD_SPECS))
  • content/docs/protocol/objectui/concept.mdx (via ListView (symbol, a field of const object REACT_FIELD_SPECS), hiddenFields (literal, a string literal in ListView))
  • content/docs/protocol/objectui/index.mdx (via ListView (symbol, a field of const object REACT_FIELD_SPECS))
  • content/docs/protocol/objectui/layout-dsl.mdx (via userFilters (literal, a string literal in BEHAVIOUR_SEGMENTS))
  • content/docs/ui/pages.mdx (via userFilters (literal, a string literal in BEHAVIOUR_SEGMENTS))
  • content/docs/ui/react-pages.mdx (via ListView (symbol, a field of const object REACT_FIELD_SPECS))

⛔ 8 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/implementation-status.mdx (via ListView (symbol, a field of const object REACT_FIELD_SPECS))
  • content/docs/releases/v12.mdx (via userFilters (literal, a string literal in BEHAVIOUR_SEGMENTS))
  • content/docs/releases/v15.mdx (via ListView (symbol, a field of const object REACT_FIELD_SPECS), userFilters (literal, a string literal in BEHAVIOUR_SEGMENTS))
  • content/docs/releases/v16.mdx (via ListView (symbol, a field of const object REACT_FIELD_SPECS))
  • content/docs/releases/v17/17-0.mdx (via ListView (symbol, a field of const object REACT_FIELD_SPECS))
  • content/docs/releases/v17/17-4.mdx (via ListView (symbol, a field of const object REACT_FIELD_SPECS))
  • content/docs/releases/v17/17-5.mdx (via userFilters (literal, a string literal in BEHAVIOUR_SEGMENTS))
  • content/docs/releases/v9.mdx (via ListView (symbol, a field of const object REACT_FIELD_SPECS))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/lint/src/index.ts) — pages documenting those are invisible to this run
  • 2 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 — 17 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 a7199ef9744edf0c3ef0e381bb95a328545a3da9 → packageMentionDocs.

Which tree this was computed on

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

node scripts/docs-audit/affected-docs.mjs --json a7199ef9744edf0c3ef0e381bb95a328545a3da9

⚠️ 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 a7199ef9744edf0c3ef0e381bb95a328545a3da9 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 20, 2026 12:32
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 20, 2026 12:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation protocol:ui size/m tests tooling

Projects

None yet

2 participants