Skip to content

feat(spec)!: a type: 'chart' list view whose effective binding names no dataset is refused at every list-view door - #22528

Merged
objectstack-fleet[bot] merged 16 commits into
mainfrom
claude/issue-22491-chart-view-needs-dataset
Oct 10, 2026
Merged

objectstack-fleet[bot] merged 16 commits into
mainfrom
claude/issue-22491-chart-view-needs-dataset

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #22491
Clause-②: yes (narrowing)

Claim 6083229542 (session session_01KNKBCRDJCu5tGy3TEbvtrF, branch claude/issue-22491-chart-view-needs-dataset), triage direction 6082420385. Every list-view door now refuses a type: 'chart' list view whose effective chart binding names no dataset, with the binding to declare. No default binding is fabricated.

What changes

  • The rule (packages/spec/src/ui/view.zod.ts): checkListViewChartBinding, exported, attached by identifier at the same three list-view doors as checkListViewCalendarVisualization:

    • ListViewSchema, which also covers views[].list / listViews, a view item config and defineView;
    • ObjectListViewSchema (objects[].listViews);
    • the flattened list overlay member of the view write door (PUT /api/v1/meta/view/:name).

    One rule judges every door. There is no door-specific second contract.

  • The effective binding is the renderer's: the chart block, else the legacy options.chart bag, with the block replacing the bag whole. objectui packages/plugin-list/src/ListView.tsx resolveListChartBinding, schema.chart || schema.options?.chart || {}: :207 at this repo's .objectui-sha pin f0268ad7, :260 at objectui 2a48bd40. Read only; nothing in objectui was touched.

    • no block and no bag: one custom issue at chart. Its first sentence is "This list view is type: 'chart' but declares no chart block, so it binds no dataset and there is nothing to plot." The remedy names chart.dataset and chart.values.
    • no block, and a bag missing dataset or values: one custom issue per missing key, at options.chart.dataset / options.chart.values. Only the overlay carries the bag; the two authoring doors refuse options by name.
    • a declared chart block: left to its own strict schema, which already requires both keys at chart.dataset / chart.values, so nothing is reported twice.
  • The overlay reads type on the input side, as checkListOverlayTypeNeedsColumns does. A patch that names no type (the console's toolbar save) is not judged; the view it shadows decides.

  • The chart slot gains a .describe() saying a chart view must bind one. The reference pages regenerate with it.

  • Ledger: the D3 semantic entry view-chart-binding-dataset-required (step 18). It has no rationale fragment, so the hand-written region of registry.ts is untouched and only the generated region moved. There is no D2 conversion, because only the author knows the dataset. The changeset is major on @objectstack/spec, because pre mode next takes a breaking change at major. It carries the registered ADR-0087 marker.

Premise, measured on origin/main @ e148ca98 (before)

body overlay member ViewMetadataSchema union ListViewSchema ObjectListViewSchema
type: 'chart', no block ACCEPT ACCEPT ACCEPT ACCEPT
options.chart: { chartType } only ACCEPT ACCEPT refused (options unknown key) refused (same)
chart: { chartType } refused at chart.dataset / chart.values refused refused refused

After the change, the first two rows are refused at chart and at options.chart.dataset + options.chart.values. The container member (list) and the view item member (config) refuse them too, at list.chart / config.chart.

Stored rows (Zone 2 item 4), measured

applyConversionsToStoredItem is the one rehydration primitive. It replays only the conversion registry, and no entry touches a list-view chart block. The read door does not re-validate: it decorates.

meta-view-chart-binding.test.ts measures this through the real PUT / GET /api/v1/meta/view/:name over SQLite. A row stored with no binding, planted directly in sys_metadata, is served 200 as stored, with _diagnostics.valid: false and the same issue at chart. Re-saving the body that was read answers 422 INVALID_METADATA at chart. So no stored view becomes unreadable, and each one is refused on its next save. The ADR-0087 disposition is registered view-chart-binding-dataset-required; the changeset states it, along with this read-side behaviour.

Pins

  • packages/spec/src/ui/view-chart-binding.test.ts, across all three doors:
    • type: 'chart' with no block is refused at chart. The test checks the code, the path and the message's first sentence, and that the remedy names both keys.
    • Control: a complete block saves and is kept.
    • A declared incomplete block gets no second issue.
    • Grid views are not judged, including one that lists chart in allowedVisualizations.
  • Overlay only:
    • a bag holding only chartType is refused at both keys;
    • a bag with dataset but no values is refused at values;
    • a complete bag is accepted and kept;
    • an incomplete bag under a complete block is accepted, because the block replaces the bag whole;
    • a patch that names no type is not judged;
    • a drift pin derives ListChartConfigSchema's required keys and asks the bag for exactly those.
  • packages/spec/src/ui/object-refinement-check-exports.test.ts catalogues the export:
    • parity and bijection over its fixture matrix;
    • three attachments by identifier, chained after the calendar check;
    • barrel identity.
  • packages/rest/src/meta-view-chart-binding.test.ts exercises the real door (RestServer route, then saveMetaItem, then ObjectQL with SQLite):
    • the ADR-0112 envelope 422 + INVALID_METADATA, the issue path, and an empty store for both refusals;
    • control 200, with the stored row carrying its binding;
    • the stored-row read and re-save described above.

Fixture triage (consumer radius, not just packages/spec)

I grepped type: 'chart' across examples/**, packages/** and skills/**:

  • The two chart list views in examples/app-showcase (project.view.ts, task.view.ts) bind a dataset and measures. Importing the showcase's src/ui/views against the built spec runs defineView at import and parsed 26 list views, 2 of them charts.
  • The lint fixtures (showcase-shape.fixtures.ts, validate-chart-bindings.test.ts) bind a dataset and a measure.
  • The conversion-registry and additional-types hits are dashboard widgets and a metadata type name, not list views.

Two spec pins parsed a block-less chart view as a stand-in for "every type". Each was re-judged as missing a declaration, and now carries the binding:

  • view.test.ts, "keeps every surviving view type accepting exactly as before";
  • view-form-pagination.test.ts, "is backed by the door: type 'chart' parses a pagination block". Studio's view form does have a chart section (view.form.ts), so a Studio author can declare the binding.

Reverse verification

I ran this once in a second worktree at 7836b326, after committing the fix. The mutation went through scripts/ablation-replace.mjs: the check's guard line became an unconditional return, the anchor count went 1 to 0, and the blob went 0915637f to d75f30ac. The two spec pin files then ran 10 failed / 204 passed:

  • every refusal pin went red, at all three doors and in the bag cases;
  • the export parity and bijection legs went red;
  • every control stayed green.

The direction was the expected one: the pins turned red. The restore was proven: the blob equals HEAD and git diff HEAD is empty. These spec tests resolve ./view.zod from source, so no dist rebuild was involved. The REST pin reads the built spec and was not ablated.

Verification (local; first round at 7836b326, patch round at e295f361; CI owns the full farm)

Build: pnpm --filter @objectstack/spec build, then check:generated found 3 of 15 artifacts stale (api-surface, export-origins, docs). --fix regenerated only those three, and a re-check reported all 15 up to date. After that I built the dependency closure of rest, lint and metadata* with turbo: 24/24 tasks.

run result
@objectstack/spec tests, 4 shards (--project local) 158/158 files (5261 tests), 157/158 then re-run, 158/158 files (4012 tests), 156 + 1 skipped (5032 tests). The one shard-2 red was the pagination pin re-judged above; it was fixed in 7836b326, and the four touched spec test files re-run at 7836b326 gave 4/4 files, 732 tests.
@objectstack/spec typecheck (tsc + scripts + test layer) exit 0
@objectstack/lint tests 130/130 files, 5943 tests
@objectstack/rest tests (--project local, full) 269/269 files, 4979 passed / 326 skipped, including meta-view-chart-binding.test.ts
@objectstack/rest typecheck (tsc + test layer) exit 0
@objectstack/metadata-core / metadata-fs tests 17/17 files (387 tests), 10/10 files (70 tests)
@objectstack/metadata / metadata-protocol (narrowed, see below) 4/4 files (51 tests), 31/31 files (1247 tests)
examples/app-showcase views, imported against the built spec 26 list views (2 charts) parse
eslint on the 8 changed .ts files, --format json 8 files, 0 errors, 0 warnings
dispatch-gates --commands (114 derived) + the 49 artifact-roster commands 156 at exit 0. check:skill-examples re-ran at 0 after its client-react prerequisite was built. check:dual-build-cjs-loads is NOT MEASURED: it exits 3 with PREREQUISITE NOT MET because it needs every package built. Three roster guards (closing-target-claim, partof-closing-keyword, single-claim-paths) exit 2 NOT WIRED without a PR; partof-closing-keyword was re-run on this body and passed (exit 0). --ran reports "114 derived famil(ies) accounted for — 113 run, 1 NOT-MEASURED".

Declared narrowing (coordinator-approved): metadata and metadata-protocol ran their view-door test files only; the full suites are CI's.

  • Why the narrowing cannot hide a regression: the new check fires only on type === 'chart'. git grep -nE "['\"]chart['\"]" over packages/metadata, packages/metadata-protocol, packages/metadata-core, packages/metadata-fs, packages/rest and packages/objectql, leaving out my own new test, finds two hits. Neither is a list view: one is a report container in protocol.invalid-metadata-422-face-inventory.test.ts, the other a zod union member in zod-union-fields.test.ts.
  • A search for iteration of the list-view type enum over the same packages (shape\.type|overlayTypeValues|'gantt', ?'map') finds only page type reads. The control term in the same corpus, viewKind|ListViewSchema|ViewMetadataSchema, hits 4 test files in metadata and 29 in metadata-protocol.
  • Files run instead: those 33, plus protocol.stored-conversions, protocol.invalid-metadata-422-face-inventory and protocol.read-decorations (31 in metadata-protocol once de-duplicated). metadata: metadata-manager-views-by-object-container, plugin-artifact-view-container-object, view-container-name, view-expand.
  • The lint narrowing is a measurement. The population comes from eslint.config.mjs's own files: ['**/*.{ts,…}'] objects; the file count comes from --format json. That config never enables type-aware linting (no parserOptions.project; it says so at :327), so the diff cannot move the verdict on any file it does not touch.

Patch round, at e295f361.

  • Sync. Merged origin/main at ee8751d4 with scripts/pm/os-regen-merge.sh (no rebase) into merge commit be6f3e23. That brought in protocol 18 from 4e9fe9ff.
    • The script's step-3 hand-off commit d3900905 regenerated content/docs/references/data/object.mdx from the merged tree, after a spec build. That build ran after the merge commit, never in the MERGE state.
    • 742960d6 contains only gen:spec-changes + gen:upgrade-guide output.
    • Every step-18 entry id on origin/main is still present after the merge; the only id added is view-chart-binding-dataset-required.
  • Changeset (b83e8aaf): Clause-②: yes (narrowing), and one sentence naming the new export checkListViewChartBinding. check-changeset-no-major, check-adr-0087-registration ("registered view-chart-binding-dataset-required (new here)"), check-empty-changeset and check:changeset-gate-self-tests each exit 0.
  • Test Core fix (e295f361): the D3 entry's reason named an objectui tracker id, which the migrate meta guidance must not print. That wording is gone, and the projections were regenerated with gen:migration-registry / gen:spec-changes / gen:upgrade-guide. No other # plus 4–5 digit id is left in this diff's printed text (refusal messages, .describe(), changeset, entry strings).
  • Re-run, one lock call per suite:
    • the four touched spec test files: 4/4 files, 732 tests (at b83e8aaf);
    • spec typecheck: exit 0 (at b83e8aaf);
    • rest meta-view-chart-binding.test.ts after a turbo build of @objectstack/rest^... (24/24): 1/1 file, 4 tests (at b83e8aaf);
    • packages/cli test/migrate-meta-engine-guidance.test.ts (--project integration), after a turbo build of @objectstack/cli... (59/59): 1/1 file, 3 tests (at e295f361).
    • e295f361 changes only the entry's prose and the three projections, so the b83e8aaf runs read identical inputs.
  • Gates at e295f361: all 161 union commands (dispatch-gates --commands, 114 derived, plus the 49 artifact-roster commands) were run, each exit code captured before any pipe.
    • 158 exit 0, including check:spec-changes, check:upgrade-guide, check:migration-registry, check:generated, check:error-code-provenance, check:skill-examples and check:dual-build-cjs-loads.
    • The three PR-context guards (check-closing-target-claim, check-partof-closing-keyword, check-single-claim-paths) exit 2 without a PR. Re-run against PR feat(spec)!: a type: 'chart' list view whose effective binding names no dataset is refused at every list-view door #22528, each exits 0.
    • --ran: "✓ dispatch-gates --ran: 114 derived famil(ies) accounted for — 114 run, 0 NOT-MEASURED (a DERIVED zero — all 114 recorded an exit code and none of them is 3)."
  • Declared to CI: the full spec, lint, rest, metadata* and cli suites at the merged head.

Acceptance notes

  • ADR-0078 §4 says there is "no hard Zod .refine() that rejects existing metadata at registration". This rule sits at the parse doors (authoring, defineStack, the write door). That is the same placement as the calendar binding check and the joined-report blocks[i].dataset refusal. The runtime registration seam is untouched: views do not register through it, and stored rows keep loading, with _diagnostics.
  • Scope: only type: 'chart' is judged. A grid that only offers a chart in appearance.allowedVisualizations is a degrade, not a dead screen: objectui's availableViews gate asks the same resolver and never offers an unbound chart. This is recorded and pinned as a deliberate non-rule.
  • checkViewCompleteness stays silent on chart. The schema doors now refuse the block-less view, so a completeness finding would double-report it.
  • Observation, not filed (read-only, unreproduced): objectui packages/app-shell/src/views/ObjectView.tsx :3162 reads only viewDef.chart for a type === 'chart' view, not options.chart. A view whose only binding is a complete bag therefore renders the unbound refusal on that route, but plots on ListView's. That makes two precedences in one renderer repo. It belongs with objectui's domain:spec seat, which files the ListViewSchema mirror after this lands.
  • #22491 names only this card. objectui's mirror is not part of this PR.

Generated by Claude Code

@github-actions

github-actions Bot commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec, touching 11 documentable anchor(s). ⚠️ 3 changed file(s) yielded no anchor (packages/spec/api-surface/ui.json, packages/spec/export-origins/ui.json, packages/spec/spec-changes.json), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

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

  • content/docs/concepts/metadata-lifecycle.mdx (via /api/v1/meta/view/:name (route, a path literal in acceptanceCriteria; a path literal in semantic))
  • content/docs/kernel/services-checklist.mdx (via /api/v1/meta/view/:name (route, a path literal in acceptanceCriteria; a path literal in semantic))
  • content/docs/protocol/objectui/index.mdx (via /api/v1/meta/view/:name (route, a path literal in acceptanceCriteria; a path literal in semantic))
  • content/docs/ui/forms.mdx (via /api/v1/meta/view/:name (route, a path literal in acceptanceCriteria; a path literal in semantic))

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

  • content/docs/releases/v12.mdx (via ObjectListViewSchema (symbol, a top-level const))
  • content/docs/releases/v17/17-1.mdx (via ListViewSchema (symbol, a top-level const))
  • content/docs/releases/v17/17-5.mdx (via ListViewSchema (symbol, a top-level const))
  • content/docs/releases/v17/17-6.mdx (via /api/v1/meta/view/:name (route, a path literal in acceptanceCriteria; a path literal in semantic))

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
  • 3 changed file(s) yielded no anchor (packages/spec/api-surface/ui.json, packages/spec/export-origins/ui.json, packages/spec/spec-changes.json) — pages documenting those are invisible to this run
  • 4 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 b53b949a1518ccd5bbfb2f3b8a6fac4eaa9c2da2 → packageMentionDocs.

Which tree this was computed on

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

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

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

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 2c0f7aa07f660e869af379fc18b76b9bb15782b2
Local-runs: none

PR #22528 (card #22491). Net diff of the branch against its merge-base with origin/main, 4638625e07 (equal to the PR's recorded base): 16 files, +671 / −28, 699 changed lines, under 3,000. Inputs: the card body and its ten comments, the PR body and its one comment, the file list, the diff, and the 35 check-runs on this head. Check-runs: 33 success, 2 skipped (Console Pin Gate, the diff moves no pin; Packed-tarball smoke, opt-in). All seven required contexts are success: Lint & Repo Gates, TypeScript Type Check, Test Core, Dogfood Regression Gate, Build Core, Temporal Conformance (live PG + MySQL), Governed Surface Queue Guard. No governed surface is in the file list.

① Derived judgments

Accept-set changes (all narrowings, every one judged on type: 'chart' only):

  1. ListViewSchema (the authoring door: defineView, views[].list / listViews, a view item config) refuses type: 'chart' with no chart block, one custom issue at chart. Right. The chart slot already required dataset and values, so the block-less view was the one shape that met the type without meeting the binding. The two authoring doors are strictObject and refuse options by name, so the bag branch is unreachable there, as the exports-test fixture comment states.
  2. ObjectListViewSchema (objects[].listViews, built by .omit() from the unrefined shape) re-attaches the check. Right. Same door-by-door re-attachment as the calendar check; the exports test pins exactly one declaration and exactly three attachments by identifier.
  3. The flattened list overlay member of ViewMetadataSchema (PUT /api/v1/meta/view/:name) refuses the same, and when no chart block is declared it also judges the legacy options.chart bag: one issue per missing required key at options.chart.dataset / options.chart.values. Right. ListViewOverlayOptionsSchema judges each options.KIND as a .partial() underlay, so { chartType } passed the shape before; the renderer (resolveListChartBinding, schema.chart || schema.options?.chart) takes the bag whole when no block exists, so the bag owes the block's required keys. A drift pin derives those keys from ListChartConfigSchema (dataset, values; chartType defaults to bar, dimensions optional). The check is chained after checkListOverlayTypeNeedsColumns and the calendar check and before .overwrite(applyListOverlayTypeDefault), so a patch naming no type is not judged; pinned.
  4. A declared chart block, complete or not, is left to its own strict schema; no second issue at chart. Right, pinned (chart.dataset / chart.values only).
  5. A grid that merely lists chart in appearance.allowedVisualizations is not judged. Right as a deliberate non-rule recorded with its exempting evidence (objectui's availableViews gate), the ADR-0078 §6 form; pinned.
  6. Stored sys_metadata rows are neither rewritten nor refused on read: served as stored with the issue in _diagnostics, refused on the next save. Right. applyConversionsToStoredItem replays only the conversion registry and no entry touches the chart block; the REST pin measures it at the real door over SQLite (planted row, GET 200 with _diagnostics.valid: false, re-PUT 422 INVALID_METADATA at chart, empty store on both refusals).

Public-surface changes:

  1. One new export, checkListViewChartBinding, from @objectstack/spec/ui. Right and owed: objectui's ListViewSchema mirror is built from ListViewSchema.shape, which drops object-level checks, so the mirror re-attaches the schema's own rule by import. api-surface/ui.json +1 (checkListViewChartBinding (function)), export-origins/ui.json +1 (src/ui/view.zod.ts#checkListViewChartBinding (function)); nothing removed or renamed. Barrel identity is pinned.
  2. The chart slot's .describe() text changes and is projected into references/ui/view.mdx (6 rows), references/api/protocol.mdx (2 rows) and references/data/object.mdx (1 row, the listViews embedding). Every changed row is that one description string and nothing else. Right, a pure projection.
  3. No authorable key is added or removed: authorable-surface/, liveness/ and the JSON-schema manifest are untouched, and the dropped-refinements ledger owes no row (a second root-level superRefine on ListViewSchema adds no new site). Right; TypeScript Type Check, which runs every spec artifact gate, is green on this head.
  4. Refusal text carries no tracker number: the two message constants, the .describe(), the D3 entry's four prose fields and the changeset are clean; the #22491 markers in the diff are code comments and test titles. Right; Lint & Repo Gates (check:doc-authoring) and Test Core (the cli migrate meta guidance pin) are green.
  5. Consumer radius: the two showcase chart views and the lint fixtures bind a dataset; two spec pins that used a block-less chart view as a stand-in for "any type" (view.test.ts, view-form-pagination.test.ts) now carry the binding. Right: both are declarations the fixtures were missing, not loosened pins. Nothing is removed, so the pinned objectui checkout imports nothing this diff takes away.

② Semver level

  • Packages that publish something in this diff: @objectstack/spec only. packages/rest gains a test file and no source; content/docs/** and docs/** are not packages. Exactly one changeset entry is owed and exactly one exists: .changeset/22491-chart-list-view-binds-a-dataset.md, '@objectstack/spec': major.
  • Clause-②: yes (narrowing): yes because one export is added (the public surface widens by one name, so at least minor); (narrowing) because the accept set shrinks on a published authoring surface and on the write door, which is BREAKING. The line reads identically on the PR body (line 2), in the changeset, and on the card's claim comment 6083229542, whose correction line owns the earlier no (narrowing). Right.
  • Level: major, correct in pre mode (.changeset/pre.json is mode: pre, tag: next, on main and on the head); Check Changeset is green. The changeset body carries the migration (FROM a block-less or bag-only chart view TO a top-level chart block naming dataset and values, with the one-line fix), the stored-row behaviour, the census, and names the new export. Right.
  • ADR-0087 disposition: exactly one marker in the changeset body, registered view-chart-binding-dataset-required. The D3 entry, judged:
    • Shape: packages/spec/src/migrations/entries/semantic/18.view-chart-binding-dataset-required.ts, a SemanticMigration with id, surface, replacement, reason, acceptanceCriteria; no conversionIds (no D2: only the author knows the dataset) and no relevantWhen (list views live under several stack keys and in stored metadata, so always-listed is the conservative reading). Same shape as the precedent ui-report-joined-block-dataset-required. No tombstone and no RETIRED_KEYS_BY_MAJOR row, correctly: no key is removed.
    • Printed guidance: surface has no backticks and no pipes (it renders in a code span); none of the four fields carries a tracker number; replacement states the FROM → TO and the one-line fix; acceptanceCriteria names the doors and the stored-row behaviour.
    • Registration: the filename is 18. plus the id; the entry lands at registry.ts line 23047, inside the os-generated semantic:18 region (lines 6905 to 23635), and the hand-written region is untouched (the registry diff is additions only; no STEP18_RATIONALE fragment, which is optional and matches the view-door precedent). spec-changes.json carries it at both projection sites (+14) and docs/protocol-upgrade-guide.md carries the entry, "Why not automatic" and "Done when" (+3), each beside its siblings as main had them at the merge-base, nothing hand-written. check:migration-registry, check:spec-changes, check:upgrade-guide and check:generated are green inside TypeScript Type Check.
    • One nit, not blocking: surface is prose with no leading dotted path, where siblings lead with one (ui.ViewFilterRule …, reports[].blocks[].dataset …). It renders and the guidance test passes; a later edit may lead with ui.ListView.chart.

③ Boundary flags

open_questions is empty in all four os-dev-report comments (6087996555, 6089488712, 6091517144, 6091857861). The dev's declared deviations, acceptance notes and out-of-scope findings, each answered:

  1. File surface widened by one test-only file, packages/rest/src/meta-view-chart-binding.test.ts. Accepted: it is the ADR-0112 envelope pin at the real door (422 + INVALID_METADATA + empty store) plus the stored-row measurement the triage direction asked for; no rest source moved.
  2. The rule refuses more than the card named (a bag with dataset but no values). Right (judgment 3); the seat accepted it and the drift pin keeps it honest.
  3. metadata / metadata-protocol suites narrowed locally. Superseded: Test Core ran in full on this head and is green.
  4. check:dual-build-cjs-loads NOT MEASURED in round 1. Closed: measured at exit 0 in the later rounds, and CI covers it.
  5. No STEP18_RATIONALE fragment. Right: optional, the view-door precedent carries none, and it keeps the hot hand-written region untouched.
  6. chart.zod.ts untouched. Right: the effective-binding rule belongs on the list-view doors.
  7. The os-regen-merge.sh step-3 hand-off was committed by hand after a pre-commit refusal (twice), and the last sync's hand-off took main's side of the two projections before the regen commit restored this PR's entry. Accepted: the projections check clean on this head, the regen commit 2c0f7aa07f carries only this PR's entry, and the survival assertion named every sibling id.
  8. ADR-0078 §4 and its non-goal ("no hard Zod refinement that rejects existing metadata"). Answered, not a reversal: the governing path is ADR-0049's enforce arm with an ADR-0087 D3 entry and the maintainer ruling 「短期不考虑渐进」, the same placement as the two landed precedents (the calendar binding check and the joined-report block refusal); the runtime registration seam is untouched, views do not register through it, and the REST pin proves stored rows keep loading with _diagnostics. A block-less chart view is a dead screen, not a benign-incomplete instance.
  9. Scope limited to type: 'chart'; checkViewCompleteness stays silent on chart. Right: no double report, and the non-rule is pinned with its evidence.
  10. Out of scope, carried not filed: objectui app-shell ObjectView.tsx reads only viewDef.chart for a chart view while plugin-list reads chart || options.chart. Escalated to its carrier, not blocking: the spec follows the triage direction and the pinned ListView resolver, so a complete bag-only overlay is accepted here; if the app-shell reading holds, that view renders unbound on that route, a two-precedence inconsistency inside objectui that belongs with the objectui domain:spec seat's ListViewSchema mirror card (the carrier named in the reports and the seat notes). It is unreproduced, so it has no reach:; the mirror card should measure it before the mirror lands.
  11. functional-completeness.test.ts still titles chart among the types with no binding block to demand. Cosmetic: the behaviour it pins is still correct; a one-line title fix may ride the mirror follow-up.
  12. Landing note for the owning seat, not a verdict matter: since the merge-base 4638625e07, main has moved on the same hot files (registry.ts, spec-changes.json, the upgrade guide, view.zod.ts, two reference pages; fix(runtime)!: the /i18n dispatcher domain refuses an anonymous caller, with the console pin moved past the sign-in companion (#22432) #22496, feat(metadata-protocol,runtime,service-automation,spec)!: the protocol refuses every organization-scoped write; an uninstall is environment-wide (ADR-0131 D6/D12) #22515, tooling(pm): dispatch-gates — the hand-written tables move out as data, the self-test out as a module with fast/slow tiers, derivation byte-identical #22531). The PR reads mergeable_state: clean; the entries README measures a driver-less text merge of far-apart registry ids as byte-identical to a regeneration, and since spec(changes): generate the per-major spec-changes section and the upgrade guide at publish; the pull request generates both in memory and renders the diff (#22449 B′, condition 1) #22533 check:spec-changes / check:upgrade-guide generate in memory. A re-sync that is a pure regeneration may carry this record under the pure-regeneration rule; any other push re-owes the review.
  13. The checkListViewChartBinding docblock cites objectui line numbers at pin f0268ad7, which main has since moved to 47b1f0bb. Cosmetic: the objectui-main citation (2a48bd40) stands beside it.

Implemented-by: claude/issue-22491-chart-view-needs-dataset
Reviewed-by: session_01KNKBCRDJCu5tGy3TEbvtrF

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Landing pre-checks at 2c0f7aa07f, by the owning seat: queued under the maintainer's rule-A relaxation

domain:spec seat 3 (#18883) · zhuangjianguo · session session_01KNKBCRDJCu5tGy3TEbvtrF · 2026-10-10T01:08Z · holder of claim 6083229542.

pr_ready and automerge_enable follow.


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 10, 2026 01:09
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 10, 2026 01:09
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 10, 2026
Merged via the queue into main with commit 20e7d52 Oct 10, 2026
37 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-22491-chart-view-needs-dataset branch October 10, 2026 01:35
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/l tests tooling

Projects

None yet

2 participants