Repository navigation
feat(lint)!: refuse a widget filterBindings key that names no declared dashboard filter - #27527
Conversation
…key naming no declared dashboard filter (#27445) Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WGufzeTeGTp7gp3ECrLPMf
📓 Docs Drift CheckThis PR changes 2 package(s): 2 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 3 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 166 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 792b6163161bd51e8cb244658913882716951759 && git checkout 792b6163161bd51e8cb244658913882716951759
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 3604371fd229511aa028cc660415f15bc8f74a10 4759433d4c2601f6e4a20f1d2c105391232cc7f8 && git checkout -B drift-repro 3604371fd229511aa028cc660415f15bc8f74a10 && git merge --no-ff 4759433d4c2601f6e4a20f1d2c105391232cc7f8
node scripts/docs-audit/affected-docs.mjs --json 3604371fd229511aa028cc660415f15bc8f74a10
|
|
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U Contract-review-claim: Triage seat, under gap rule (b) (#27062): this head is over 60 minutes old with no record and no claim. The review runs at Generated by Claude Code |
Contract reviewServed-tier: Inputs read: card #27445 body and both comments (triage 2026-10-29T08:09Z, claim 09:31Z); PR #27527 body, file list (12 paths), net diff vs ① Derived judgments
② Semver levelChangeset Clause-②: no (narrowing) — the line matches what the diff publishes: nothing is widened; one accept set is narrowed at every ③ Boundary flags
④ Design judgment
Verdict basis. Both Implemented-by: VERDICT: PASS Generated by Claude Code |
席位核对 · ACCEPT ·
|
Requested by Yibo Wang · project thread
Fixes #27445
Triage-ref: 6491434802
Clause-②: no (narrowing)
Metadata-protocol: narrowed
Skills-docs: none —
skills/objectstack-ui/rules/dashboards.mdalready teaches that afilterBindingskey is the declared filter'sname(defaulting tofield); nothing there teaches an undeclared key, so noskills/**edit. Docs updated here:content/docs/ui/dashboards.mdx,content/docs/deployment/validating-metadata.mdx.维护者速读
dashboard-filter-binding-unknown(error)。部件filterBindings的键若不是该仪表盘已声明的过滤器名,os validate/os build/os lint和元数据发布门(422 INVALID_METADATA)都会拒绝;草稿照常保存,_diagnostics给出同样的结论。消息写明部件 id、错误的键、已声明的过滤器,并给出 Did you mean。{ regoin: false })会让部件仍被那个过滤器筛选,数字悄悄变了。ui-dashboard-filter-binding-unknown-refused,无 D2(盘点没有任何生产者写出未声明的键)。已存的sys_metadata仪表盘照常加载,下次保存/发布才会被拒。回滚即 revert。packages/lint/src/validate-widget-bindings.ts的 (a-1) 段。cd packages/lint && pnpm exec vitest run src/validate-widget-bindings.test.ts -t "dashboard-filter-binding-unknown",预期全部通过。What changes
One new error id in
validateWidgetBindings. For every widget, each key offilterBindingsmust name a filter the dashboard declares. The declared names are the ones the rule's existingdashboardFilterDefsbuilds, the same set objectui'sresolveDashboardFilterDefsbuilds at render time: eachglobalFilters[]entry under itsname(itsfieldwhen unnamed), anddateRangeonly when the dashboard declaresdateRange. A key outside that set is reported atdashboards[i].widgets[j].filterBindings.<key>:with the fix
Rename the key to a declared filter name, or delete the entry. Did you mean "region"?(adateRangekey on a dashboard with nodateRangegets "DeclaredateRangeon the dashboard, or delete thedateRangeentry."). The check runs before the dataset limb because it needs no dataset, and it is not suppressible.Why error, not the
chart-field-unknownwarning tier. #15463 demotedchart-field-unknownbecause its three keys only re-dress the same numbers. AfilterBindingskey decides which rows the widget counts: a misspelled opt-out leaves the widget scoped by the filter the author opted it out of, and a misspelled re-target leaves the filter on its default field. Triage 6491434802 graded it a refusal at draft and publish.Route. The rule rides
validateWidgetBindings, which is already on thedashboardruntime-gate path with per-rulesurfaces(the 2026-08-15 ruling's class), so the publish door refuses it with no new wiring and a stored row is never judged on load. No spec superRefine:DashboardWidgetSchemacannot see its dashboard's filters, and a parse refusal would move the load path.packages/lint: the rule andDASHBOARD_FILTER_BINDING_UNKNOWN(barrel-exported), itsos explainentry inrule-explanations.ts, the module docblock bullet.packages/spec:DashboardWidgetSchema.filterBindingsTSDoc and.describe()name the rule and the valid keys (content/docs/references/ui/dashboard.mdxregenerated); ADR-0087 D3 entry18.ui-dashboard-filter-binding-unknown-refused(no conversion: only the author knows which filter a misspelled key meant).fieldResolves" table and a paragraph under Per-Widget Filter Bindings (dashboards.mdx); a paragraph invalidating-metadata.mdx.27445-dashboard-filter-binding-unknown.md:@objectstack/lintminor,@objectstack/specminor, markerregistered ui-dashboard-filter-binding-unknown-refused.Census (Phase 1)
Measured on main
273137d2and the public HotCRM clone at47a0666:showcase_revenue_pulse(keysdateRange,region; declares both) andshowcase_ops_dashboard(task_status; declared); platformsystem_overview(created_at; an unnamedglobalFiltersentry whose name defaults to its field); HotCRMexecutive_dashboard,sales_dashboard,crm_overview_dashboard,service_dashboard(dateRange,owner_id,lead_source,type,priority; all declared, the unnamed entries by field).docs/**andskills/**snippets are widget-array fragments. So no D2.dateRangeis valid only when declared: lint'sdashboardFilterDefs, objectui'sresolveDashboardFilterDefsand@objectstack/verify's dashboard-window proof all build thedateRangedef only whendash.dateRangeis set, so an undeclared one was inert everywhere.filterBindingsrows only for declared filter defs. Renaming or removing a global filter does not prune existing widget bindings; that dashboard's next save is now refused naming the stale key, which the widget's genericFilter Bindingsform row can delete.Tests
packages/lint/src/validate-widget-bindings.test.ts(new describe, 12 cases): the card's body (exact message, path and fix), re-target spelling with one finding per key, declared keys includingdateRangepass, an unnamedglobalFiltersentry bound byfield, a customnamereplaces the field as the key,dateRangeonly when declared, a dashboard with no filters, judged with no dataset and not suppressible, silent withoutfilterBindings, gatesvalidateandbuild, theos explainentry.packages/lint/src/runtime-gate.test.ts: the door accept-set pin goes from five ids to six, and a new case pins both halves at the door (a declared binding publishes;regoinis refused at its path with the declared list and the Did you mean).packages/metadata-protocol/src/protocol.draft-publish-verdict.test.ts: a seventh case on the real save/read/publish seams (the draft saves, readsvalid: falsewith the publish's words, the publish answers422 INVALID_METADATA); and a stored-row pair: a row written with the authoring gate off loads with{ regoin: false }intact, and its next save is refused 422 naming the key and(declared: region). The stub engine's matcher learned$in, which the active read's type clause uses.continue, 10 lint cases go red; restored, green.Verification
Head
4759433d, off main273137d2.os-verify-lock: build of the@objectstack/lint...and@objectstack/metadata-protocol...closures exit 0;@objectstack/speccheck:generatedexit 0 (aftergen:docsfor the describe);@objectstack/spectest(750 files, 22313 tests) andtest:repo(86 files, 1419 tests) exit 0;@objectstack/linttest (177 files, 7602 tests) and typecheck exit 0;@objectstack/metadata-protocoltest (279 files passed, 4 skipped; 21777 tests) and typecheck exit 0.node scripts/pm/dispatch-gates.mjs --commands: 107 derived families, each run with its exit recorded. 103 exit 0 on the first pass; four exit 3 for prerequisites (check-plugin-teardown-shape --self-testneeded its pinned fixture commit fetched;check:skill-examples,check:dual-build-cjs-loadsandcheck:lean-entry-closureneed a full build). Aftergit fetch origin 621a4876and a fullturbo run buildunder the lock (11m38s on a shared box), all four exit 0.dispatch-gates --ran: 107 derived, 107 run, 0 NOT-MEASURED, 0 UNRUN, all exit 0.Human Minimum
Each item is one rule from
CONTRIBUTING.md › Human Minimum,
which links the AGENTS.md text that states it.
packages/specholds no runtime logic.pnpm lintpasses. (No formatter ran. ESLint on the changed files: 0 errors;pnpm lintis CI's run.)main, one pull request.packages/spectouched: its generated artifacts are regenerated and committed.content/docs/references/,packages/*/CHANGELOG.mdorcontent/docs/releases/.🤖 Generated with Claude Code
https://claude.ai/code/session_01WGufzeTeGTp7gp3ECrLPMf