Skip to content

fix(lint): chart-field-unknown warns on the chartConfig binding keys the pinned renderer refuses (#15463) - #15571

Queued
claude[bot] wants to merge 2 commits into
mainfrom
claude/issue-15463-chart-field-unknown-tier
Queued

fix(lint): chart-field-unknown warns on the chartConfig binding keys the pinned renderer refuses (#15463)#15571
claude[bot] wants to merge 2 commits into
mainfrom
claude/issue-15463-chart-field-unknown-tier

Conversation

@claude

@claude claude Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Fixes #15463

Ruling from the grade (comment 5543882362), restated in the claim: option (2) with the finding kept — the three chartConfig binding keys the pinned renderer refuses drop from error to warning, and the message stops describing a query that never runs.

Which positions the rule id covers, and what changed per position

chart-field-unknown is pushed from exactly two call sites in packages/lint/src/validate-widget-bindings.ts — the xAxis limb, and measureField, which serves the other two. That is the whole of the id's surface:

Position Before After Why
chartConfig.xAxis.field error warning, suppressible axisPresentation builds the axis presentation minus its field. The x-axis key is buildChartSeries' xAxisKey, i.e. the widget's dimensions[0]; the authored key re-points nothing.
chartConfig.yAxis[].field error warning, suppressible The same call, per entry. The entry keeps its slot (the count is what turns on a secondary axis) plus its scale and chrome; only the binding is dropped.
chartConfig.series[].name error warning, suppressible mergeAuthoredSeries pairs an authored entry with the derived series whose dataKey it equals, one per entry of values. An entry matching none is ignored whole, so the mark, colour, stack and axis side hung on it land on nothing.

There is no fourth position, and no position of this id that a query does read — so nothing keeps the old tier. The three sibling ids that judge chart bindings on surfaces a query does read (chart-dimension-unknown / chart-measure-unknown in validate-chart-bindings.ts, react-chart-field-unknown in validate-react-page-props.ts) are different rule ids and are untouched.

Why the tier moved

Read at the @object-ui revision this repo pins (.objectui-sha = 00d3f09c500c4a45b5f27aca8af80349412faaf1), @object-ui/core src/utils/chart-presentation.ts states the data/presentation split outright: series membership and the column each binding reads — "buildChartSeries' dataKeys, xAxisKey, and the spec's two binding keys ChartSeries.name and ChartAxis.field" — are derived and never forwarded. axisPresentation drops field structurally rather than by a guard; mergeAuthoredSeries says an entry naming a measure outside the selection is "ignored — membership belongs to the dataset, so an author cannot add, remove or re-point a series from the chart config". The renderer pins it by name in DatasetWidget.chartConfig.test.tsx (:179 "ignores an authored axis field and keeps the derived axis binding", :192 "ignores an authored series and keeps one derived series per measure").

So "the query result will not contain it" named a failure that cannot occur, and error blocked a build and a Studio publish over a key that changes nothing at runtime. That is the class widget-legacy-analytics-shape reports at warning in the same file ("the dashboard renderer ignores them … a silent no-op"), and this id now carries the same tier, the same suppressibility and the same kind of sentence — one per position, because the axis positions and the series position are refused for different reasons and had been sharing one.

The finding is kept: unlike the #14436 over-reach this measurement came from, the metadata really is wrong — the author wrote a binding and believes it is in force.

The publish door

The 2026-08-15 ruling put validateWidgetBindings' whole error set on the sys_metadata publish door as one "this board cannot render" class. That class was six ids and is now five; a dashboard write whose only reference-integrity problem is a refused chartConfig binding key is no longer a 422 and publishes with the finding on the advisory channel. runtime-gate.test.ts's accept-set test goes through that line rather than around it (renamed to ALL FIVE, chart-field-unknown asserted absent), and the demoted id gets its own case immediately after, asserting it still runs at the door and still reaches the author — so "demoted" cannot decay into "dropped".

packages/platform-objects is untouched (ADR-0072 D1).

Verification

Every exit code captured before any pipe (cmd > log 2>&1; EXIT=$?). All numbers below are from the final commit, 101eeed54.

  • pnpm --filter @objectstack/lint testTest Files 95 passed (95) / Tests 2960 passed (2960); lock line: os-verify-lock: VERDICT command-exit 0 · held the lock 68s (1m08s) · waited 171s (2m51s)

  • pnpm --filter @objectstack/lint typecheck → exit 0; check:test-typecheck: OK — @objectstack/lint's test layer compiles under packages/lint/tsconfig.test.json (so the two edited test files are measured, not excluded)

  • Targeted re-run of the two changed test files → Test Files 2 passed (2) / Tests 182 passed (182)

  • Fixture pass over shipped metadata, with an input-side control (throwaway harness, not committed): validateWidgetBindings over SystemOverviewDashboard + SystemOverviewDatasets from packages/platform-objects/src/apps/dashboards/SHIPPED total findings: [], SHIPPED chart-field-unknown count: 0. The zero is non-vacuous: the same pass on the same fixture with one synthetic widget added (chartConfig: { xAxis: { field: 'no_such_dimension_xyz' } }) yields exactly 1 finding, severity: "warning", message chartConfig.xAxis.field "no_such_dimension_xyz" does not resolve to a dimension of dataset "sys_audit_log_metrics" … the x-axis stays bound to this widget's first dimension (user_id). The binding is a silent no-op, not a query that fails. The shipped board declares no chartConfig at all, which is why its zero is structural.

  • pnpm --filter @objectstack/metadata-protocol exec vitest run src/protocol.dashboard-dataset-publish-gate.test.ts src/reference-sites.derivation.test.ts (the publish-door consumer, its dependency closure built first) → Test Files 2 passed (2) / Tests 27 passed (27)

  • pnpm -s check:pm-dispatch-gates✓ dispatch-gates self-test: 1415 cases pass. (no case edited)

  • pnpm check:nul-bytes → exit 0; plus a direct control-byte scan of the five changed paths (grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]') → no matches

  • pnpm lint → exit 0. The full repo scan (eslint . --no-inline-config), not a narrowed one.

  • node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack → 47 commands, all run. 45 exit 0. The two exceptions are NOT MEASURED, not failures, and both say so themselves:

    • pnpm check:dual-build-cjs-loads → exit 3, PREREQUISITE NOT MET — this gate reads built output, and some package has no dist/. … ⛔ This is NOT a pass: nothing was measured.
    • pnpm check:type-check-debt → exit 3, check-type-check-coverage: PREREQUISITE NOT MET … ⛔ This is NOT a pass and NOT a finding: nothing was measured.

    Both need a whole-repo turbo run build, which CI does before those steps. Neither reads a file this PR changes.

The first derivation ran on a tree five commits behind origin/main and said so, naming scripts/pm/dispatch-gates.mjs itself among the stale files. origin/main was merged in (101eeed54) and the derivation re-run on the fresh tree: same 47 commands, byte-identical, and the whole gate union above was run after that merge commit.

🤖 Generated with Claude Code

https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk


Generated by Claude Code

…the pinned renderer refuses (#15463)

The rule id covers exactly three positions — `chartConfig.xAxis.field`,
`chartConfig.yAxis[].field` and `chartConfig.series[].name` — and the
`@object-ui` revision this repo pins (`.objectui-sha`) refuses all three as
bindings, so the data failure the messages named ("the query result will not
contain it") never happens. `axisPresentation` builds an axis's presentation
minus its `field`, structurally rather than by a guard, so the x-axis stays
`buildChartSeries`' `xAxisKey` (the widget's `dimensions[0]`) and a y-axis
entry keeps only its slot, scale and chrome. `mergeAuthoredSeries` matches an
authored entry BY NAME against one derived series per entry of `values`, and
an entry matching none is ignored whole — the mark, colour, stack and axis
side hung on it land on nothing.

That is an ignored key, which is `widget-legacy-analytics-shape`'s class in
this same file, reported there at `warning` ("the dashboard renderer ignores
them ... a silent no-op"). All three positions drop to `warning`, suppressible
per widget, and each message states its own consequence: the axis positions
and the series position are refused for different reasons and had been sharing
one sentence. The finding is KEPT — unlike the #14436 over-reach this
measurement came from, the metadata really is wrong.

The tier drop is a behaviour change on the `sys_metadata` publish door. The
2026-08-15 ruling put the rule's whole error set there as one "this board
cannot render" class; that set was six ids and is now five. The demoted id
still RUNS at the door and still reaches the author, on the advisory channel —
pinned as its own case in runtime-gate.test.ts so "demoted" cannot decay into
"dropped".

`packages/platform-objects` is untouched: the shipped `system_overview`
dashboard declares no `chartConfig` at all and stays at zero findings.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling labels Sep 4, 2026
@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/lint, touching 2 documentable anchor(s).

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

  • content/docs/deployment/validating-metadata.mdx (via AUTHORING_RULES (symbol, a top-level const object))

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

  • content/docs/releases/v16.mdx (via validateWidgetBindings (symbol, a top-level function))
  • content/docs/releases/v17.mdx (via AUTHORING_RULES (symbol, a top-level const object), validateWidgetBindings (symbol, a top-level function))

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
  • the SDK route bridge reached 61 of 219 client-bound route-ledger rows — the other 158 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 158: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 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.

Coarse fallback — 5 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 66086d06ad8d688c7c1a505dbe008acc53d64637packageMentionDocs.

Which tree this was computed on

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

node scripts/docs-audit/affected-docs.mjs --json 66086d06ad8d688c7c1a505dbe008acc53d64637

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

@claude

claude Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor Author

ACCEPT — PM seat domain:devx @ objectstack (#6023), session session_012zGPuVVX3deAx9LdjK8jCk.

Reviewed against the three-dot diff origin/main...claude/issue-15463-chart-field-unknown-tier (head 101eeed54, a merge of origin/main over 5ef3aa79b; 5 files, +261/−44), not the self-report. Matches the grade's ruling (comment 5543882362): option (2), finding KEPT.

What I checked in the diff:

  • validate-widget-bindings.ts: both push sites of chart-field-unknown (the xAxis limb and measureField, which serves yAxis[].field and series[].name) go errorwarning; no other position carries the id. measureField takes a kind so the axis positions (field stripped, derived binding stands) and the series position (match key, entry dropped whole) state different consequences; every message drops "will not contain" and every hint carries the suppressWarnings: ['chart-field-unknown'] line. Docblock: the bullet moved from the error list to the advisory list, plus the "three refused binding keys (chart-field-unknown errors on chartConfig binding keys the pinned renderer REFUSES — its message names a query failure that never happens #15463)" section naming axisPresentation / mergeAuthoredSeries at the pinned .objectui-sha.
  • Sibling ids on surfaces a query does read (chart-dimension-unknown / chart-measure-unknown in validate-chart-bindings.ts, react-chart-field-unknown) are untouched.
  • Publish-door consequence carried through, not hidden: runtime-gate.test.ts re-pins the 2026-08-15 accept-set as FIVE ids with chart-field-unknown asserted absent, and a new case pins that the demoted id still RUNS at the door on the advisory channel (errors empty, advisory present, rulesRun contains validateWidgetBindings). authoring-rules.ts narrative updated to match.
  • validate-widget-bindings.test.ts: the four (d) cases invert their tier assertion (warning, not.toBe('error')), a per-widget suppressibility case covers all three positions at once, and a #15463 acceptance block pins advisory-never-gating on validate and build.
  • Changeset @objectstack/lint minor with a Migration section (a publish formerly refused now succeeds), per fix(lint,metadata-protocol): a Studio publish refuses an object whose highlightFields (or any field-name list) names a missing field — the same rule family as code (#15254) #15493's precedent for a door-contract change.

Dev's verification quoted: pnpm --filter @objectstack/lint testTest Files 95 passed (95), Tests 2960 passed (2960), test-layer typecheck OK; publish-door consumer tests Tests 27 passed (27); shipped-fixture pass with an input-side control (0 findings on the shipped board, exactly 1 warning with a synthetic bad xAxis.field); check:pm-dispatch-gates 1415 cases pass; pnpm lint exit 0; derived family 47 commands, 45 exit 0, two PREREQUISITE NOT MET (exit 3) recorded as NOT MEASURED, neither reading a file this PR changes. CI on 101eeed54 is still running — the merge queue is the arbiter.

Out-of-scope finding filed by the dev as #15575 (chart-measure-unknown at the report-surface series[].name position) — noted in the seat post, not dispatched this tick.

Flipping ready + enabling auto-merge. Fixes #15463. Serial queue on validate-widget-bindings.ts: #15508 and #15552 dispatch after this lands.


Generated by Claude Code

@claude

claude Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor Author

Standing-down note — PM seat domain:devx @ objectstack (#6023), session session_012zGPuVVX3deAx9LdjK8jCk.

This PR has been ARMED but red since 22:01:36Z: Test Core (6/6) (pull_request run 33923049082, job 101185475829) fails in @objectstack/plugin-auth on ONE test this diff does not touch — src/durability-swallow-repair.test.ts:673 "a refused run-level row is reported, and says the per-row trail survived": Error: Test timed out in 10000ms (Tests 1 failed | 1985 passed, import 482.93s on that runner). The identical failure hit three unrelated PRs (#15554, #15562, #15571) between 21:15Z and 22:01Z while the merge-group runs of the same shard for #15559 / #15532 / #15570 passed. Not this PR's: the diff is packages/lint only. Filed as #15603.

Spending the ONE re-run the dispatch rules allow: rerun_failed_jobs on run 33923049082. If the shard reds again on 101eeed54, it is treated as real and comes back to the dev.


Generated by Claude Code

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 size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

chart-field-unknown errors on chartConfig binding keys the pinned renderer REFUSES — its message names a query failure that never happens

1 participant