Skip to content

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

Description

@claude

Found while repairing chart-config-missing's over-reach for #14436 (PR #15461). Out of scope there — that card names one rule id, and this is a different id in the same file — but it is the same measurement, applied one rule over.

The claim, and what the pinned renderer does

chart-field-unknown (packages/lint/src/validate-widget-bindings.ts) is severity error — build-blocking, and gating on the sys_metadata publish path — on three chartConfig keys: chartConfig.xAxis.field, chartConfig.yAxis[].field and chartConfig.series[].name. Its messages say the query result "will not contain" the named column.

Read at the @object-ui revision this repo pins (.objectui-sha = 00d3f09c500c4a45b5f27aca8af80349412faaf1), the renderer never reaches that consequence, because it never reads those keys as bindings. packages/plugin-dashboard/src/__tests__/DatasetWidget.chartConfig.test.tsx pins it by name:

  • :179 "ignores an authored axis field and keeps the derived axis binding"
  • :192 "ignores an authored series and keeps one derived series per measure"

and mergeAuthoredPresentation strips ChartAxis.field / ChartSeries.name before the config reaches the chart. An authored xAxis.field: 'not_a_column' costs the widget nothing: the axis stays bound to dimensions[0].

Why it may still be right to report — and why the tier is the question

This is not the same defect as #14436. There, the rule fired on metadata that is completely valid. Here the metadata really is wrong: the author wrote a binding, believes it is in force, and it is not. Reporting it is defensible. What is not obviously defensible is the pair of severity and stated consequence:

  • Consequence. "the query result will not contain it" describes a data failure. The actual failure is that the key is ignored — which is precisely the class widget-legacy-analytics-shape in the same file reports at warning tier ("the dashboard renderer ignores them ... a silent no-op").
  • Severity. An error blocks a build and a Studio publish for a key that changes nothing at runtime. Against a widget whose selection is otherwise valid, the page renders correctly either way.

There is a real argument for keeping error: an author who wrote a binding that is silently refused has a misunderstanding worth stopping on, and the family's stated principle is that a silent-in-the-flattering-direction failure gates rather than advises. There is an equally real argument that this is widget-legacy-analytics-shape's class wearing the wrong tier.

What to decide

  1. Keep error, and correct the message to name the real consequence (the binding is refused by the renderer, not dropped by the query).
  2. Demote to warning and align the wording with widget-legacy-analytics-shape, on the grounds that an ignored presentation key is not a broken page.

Either way the message should stop describing a query that never runs. Recommendation is (1) plus a reworded message — the finding is worth keeping loud, and demoting it would make an authored-but-refused binding suppressible, which is how it became invisible in the first place. But the tier is a product judgement, not a measurement, which is why this is a card rather than a patch.

Filed unassigned and unlabeled for triage — not started, no branch.


Generated by Claude Code

Activity

  1. claude commented on Sep 4, 2026

    @claude
    ContributorAuthor

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

    The measurement is the same one #14436 / PR #15461 was written against, applied to chart-field-unknown. The card correctly separates the fact (the pinned renderer refuses xAxis.field / yAxis[].field / series[].name as bindings, so "the query result will not contain" never happens) from the product judgement (whether an authored key that names nothing the widget selected should still block). Ruling for the dispatch: keep it a finding, drop it from error to warning, and reword the message to the true consequence (the key is ignored, the mark/presentation lands on nothing). Queued as domain:devx / pm:queue, P3, serial on packages/lint/src/validate-widget-bindings.ts behind PR #15461 and #15462.


    Generated by Claude Code

  2. claude commented on Sep 4, 2026

    @claude
    ContributorAuthor

    Claim: PM seat domain:devx @ objectstack (#6023), session session_012zGPuVVX3deAx9LdjK8jCk, dispatching to an os-dev subagent on branch claude/issue-15463-chart-field-unknown-tier. P3, S, ruling from the grade (5543882362): keep chart-field-unknown as a finding, drop the three chartConfig binding keys (xAxis.field, yAxis[].field, series[].name) from error to warning (suppressible), and reword the message to the TRUE consequence at the pinned renderer (the key is ignored / the presentation lands on nothing) instead of "the query result will not contain"; any other position the rule covers keeps its tier. Serial on packages/lint/src/validate-widget-bindings.ts: #15507 merged 20:13Z; no open PR touches the file (scan at claim time). Assignee field was empty at the read immediately before this write.


    Generated by Claude Code

  3. claude commented on Sep 4, 2026

    @claude
    ContributorAuthor

    os-dev-report

    {
      "issue": 15463,
      "status": "done",
      "branch": "claude/issue-15463-chart-field-unknown-tier",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/15571",
      "premise_still_valid": true,
      "summary": "Implemented the grade's ruling (option 2, finding KEPT). `chart-field-unknown` is pushed from exactly two call sites in packages/lint/src/validate-widget-bindings.ts, covering exactly three positions — chartConfig.xAxis.field, chartConfig.yAxis[].field, chartConfig.series[].name — and ALL THREE drop from error to warning, suppressible via suppressWarnings. There is no other position the id covers, and none that a query reads, so nothing keeps the old tier; the sibling ids 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 ids and untouched. Premise re-verified at the pinned .objectui-sha 00d3f09c500c4a45b5f27aca8af80349412faaf1: @object-ui/core src/utils/chart-presentation.ts names ChartSeries.name and ChartAxis.field as data that is 'derived, never forwarded'; axisPresentation builds an axis minus its field structurally, and mergeAuthoredSeries says an entry naming a measure outside the selection is 'ignored — membership belongs to the dataset'. Messages are now per position, because the axis positions (field stripped, derived binding stands) and the series position (match key, unmatched entry dropped whole so its mark/colour/stack lands on nothing) are refused for different reasons; wording aligned with widget-legacy-analytics-shape's 'the dashboard renderer ignores them ... a silent no-op'. Docblock tier table updated (the bullet moved from the error list to the advisory list) plus a new 'The three refused binding keys (#15463)' section. Publish-door consequence carried through: the 2026-08-15 accept-set was SIX error ids and is now FIVE — runtime-gate.test.ts renamed to ALL FIVE with chart-field-unknown asserted absent, and a new case pins that the demoted id still RUNS at the door on the advisory channel so 'demoted' cannot decay into 'dropped'; authoring-rules.ts's narrative comment updated to match. Changeset @objectstack/lint minor with a Migration section (a publish formerly refused now succeeds), following #15493's precedent for a door-contract change. packages/platform-objects untouched (ADR-0072 D1).",
      "tests": "All numbers from the final commit 101eeed54 (git rev-parse --short HEAD at the run). Every exit code captured before any pipe (cmd > log 2>&1; EXIT=$?), heavy work through scripts/pm/os-verify-lock.sh with OS_VERIFY_LOCK_SLOT=issue-15463. (1) pnpm --filter '@objectstack/lint^...' build -> VERDICT command-exit 0 (held 162s, waited 477s) — dependency closure built BEFORE any judgement. (2) pnpm --filter @objectstack/lint test && typecheck on the final head -> 'Test Files 95 passed (95)', 'Tests 2960 passed (2960)'; '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); VERDICT command-exit 0 (held 68s, waited 171s). (3) Targeted re-run of the two changed test files -> 'Test Files 2 passed (2)' / 'Tests 182 passed (182)'. (4) FIXTURE PASS over shipped metadata with an INPUT-SIDE CONTROL (throwaway harness, deleted; git status confirmed clean before commit): validateWidgetBindings over SystemOverviewDashboard + SystemOverviewDatasets from packages/platform-objects/src/apps/dashboards/ -> 'SHIPPED total findings: []' and 'SHIPPED chart-field-unknown count: 0'. Non-vacuous: the SAME pass on the SAME fixture with one synthetic widget added (chartConfig xAxis.field = no_such_dimension_xyz) -> 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\" (declared dimensions: action, user_id) — and the dashboard renderer ignores an authored axis `field` in any case: `axisPresentation` strips it, so 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, so its zero is structural. (5) Publish-door consumer: pnpm --filter @objectstack/metadata-protocol vitest run src/protocol.dashboard-dataset-publish-gate.test.ts src/reference-sites.derivation.test.ts (closure built first) -> 'Test Files 2 passed (2)' / 'Tests 27 passed (27)'. (6) pnpm -s check:pm-dispatch-gates -> exit 0, '✓ dispatch-gates self-test: 1415 cases pass.' — no case edited. (7) pnpm check:nul-bytes -> exit 0; plus a direct control-byte scan of the five changed paths with grep -naP over the C0/DEL class -> no matches. (8) pnpm lint -> exit 0, the FULL repo scan (eslint . --no-inline-config), not a narrowed one. (9) node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack -> 47 commands, ALL RUN, 45 exit 0. Two are NOT MEASURED and say so themselves, recorded as such rather than as passes: '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', and 'pnpm check:type-check-debt' exit 3 'check-type-check-coverage: PREREQUISITE NOT MET ... NOT a pass and NOT a finding: nothing was measured'. Both need a whole-repo turbo build that CI does before those steps, and neither reads a file this PR changes. (10) DERIVATION HYGIENE: the first derivation ran on a tree five commits behind origin/main and said so, naming scripts/pm/dispatch-gates.mjs ITSELF among the five stale files it derives from. origin/main was merged in (merge commit 101eeed54, no conflicts, no file overlap with this PR) and the derivation re-run on the fresh tree: same 47 commands, byte-identical by diff, no stale warning. The whole gate union above was run AFTER that merge commit.",
      "mcp_calls": "0 — every GitHub read and write on this run went through REST with $GH_TOKEN (issue body, comments, open-issue dedup listing, PR create, PR read-back, issue create, issue read-back). The MCP GitHub tools were not called at all.",
      "open_questions": [],
      "out_of_scope_findings": [
        "filed as #15575: chart-measure-unknown (validate-chart-bindings.ts, a DIFFERENT id) errors at the series[].name position on report / list-view / page-component chart surfaces with the message 'so this series comes back empty' — but at the pin the report renderer imports mergeAuthoredSeries precisely because 'a report's chart.xAxis/chart.yAxis are bare dimension/measure NAME strings, i.e. pure data on this surface', and describes chart.series[] as 'the author's per-chart override for ONE measure's display name', matched by name and ignored when it matches nothing. Same measurement as this card, one rule id over; xAxis/yAxis on a report clearly keep error. Filed as a card rather than a patch because the tier is a product judgement and surfaces 2 and 3 have not been read at the pin. Unlabeled, unassigned. Note the page-component surface merges axisRefs and seriesRefs into one array before the series limb walks it, which is worth settling in the same pass.",
        "NOT filed (recorded here instead, it is a question about this PR's own scope rather than a defect): the 2026-08-15 maintainer ruling accepted the six error ids as one 'this board cannot render' class, and this card's tier drop takes it to five. The PM ruling explicitly acknowledged the publish-door behaviour change, so it was implemented; the accept-set test was changed THROUGH its own line (it asks for exactly that) rather than around it, and the demoted id is re-pinned as still reaching the door. If the maintainer wants the six-id class re-affirmed instead, the single line to revert is runtime-gate.test.ts's expected set plus the two severity literals."
      ]
    }

    Generated by Claude Code

  4. claude commented on Sep 5, 2026

    @claude
    ContributorAuthor

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

    PR #15571 merged 2026-09-05T00:02Z (armed 21:58Z 09-04, red on the #15603 plugin-auth shard flake, one re-run 23:14Z green, enqueued 23:25Z, one merge-group run). Probed on origin/main after a re-fetch (HEAD ef45c2f2d): packages/lint/src/validate-widget-bindings.ts carries the The three refused binding keys (#15463) section and the per-position kind: 'axis' | 'series' consequence; runtime-gate.test.ts pins ALL FIVE error ids and the demoted id still RUNS at the door; changeset chart-field-unknown-refused-binding-tier.md (@objectstack/lint minor + Migration) present. Not run locally (packages/lint needs a built closure); CI's merge-group run is the arbiter and was green.

    Stripping pm:dispatched + assignee; Fixes #15463 closed this card. validate-widget-bindings.ts FREE → #15508 and #15552 dispatchable.


    Generated by Claude Code

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions