Skip to content

finding(types,plugin-charts): ObjectChartSchema anchors nothing — ObjectChart is published as props: any, and four keys its own producers write are declared on neither copy #7946

Description

@claude

Found while implementing objectui#7891 (removing ObjectView's undeclared chart.config
forward). Filed rather than folded into that PR: it is the MECHANISM that made #7891's
defect invisible, it lives in packages/types/ and packages/plugin-charts/ — both
outside that card's file surface — and acting on it is a design decision, not a rung.

What is measured, on origin/main f5d2acc

ObjectChartSchema exists twice and is exported from both — the TS interface
(packages/types/src/objectql.ts:2840) and the zod schema
(packages/types/src/zod/objectql.zod.ts:939). Neither anchors anything, for two
independent reasons.

1. The component they describe takes props: any.

packages/plugin-charts/src/ObjectChart.tsx:275
export const ObjectChart = (props: any) => {

and the published .d.ts carries that through verbatim:

packages/plugin-charts/dist/ObjectChart.d.ts:58
export declare const ObjectChart: (props: any) => React.JSX.Element;

So every schema={{ … }} literal handed to ObjectChart is type-checked against
nothing at all. MEASURED: removing both as any casts from the two object-chart
literals in packages/app-shell/src/views/ObjectView.tsx leaves tsc --noEmit GREEN,
over a program --listFiles confirms contains that file. The casts were inert; they read
as load-bearing and were not. That is why #7891's undeclared config rung survived from
the day it was written until someone read the spec by hand.

2. Four keys the producers actually write are declared on neither copy.

ObjectView's two object-chart literals write, between them, xAxisKey, series,
aggregate and filter. None of the four appears on the TS interface or the zod schema.
They are not dead either — packages/plugin-charts/src/ChartRenderer.tsx:132-134 reads
schema.series and schema.xAxisKey by name. The zod copy sits on BaseSchema, which is
.passthrough(), so they arrive and are read while their VALUES go unchecked.

3. A drift between the two copies, in passing. The zod schema declares colors (the
overloaded positional-palette / value-map union); the TS interface does not.

Why this is worth a card

objectui#6576's 2026-08-31 ruling (option A) anchored ObjectGallerySchema to
ObjectGalleryProps.schema precisely so a published widget schema would stop being a
document nothing reads, and pinned the read census in
__tests__/widget-schema-anchors-6576.test.ts. ObjectChart was not given that
treatment. ObjectChartSchema is what #6576 called the before-state: a published shape
with no prop type bound to it.

It is also the OTHER DIRECTION from objectui#7559. That card owns the input side — a
missing rung is invisible because viewDef is an untyped record. This is the output side:
an EXTRA, undeclared rung is invisible because the consuming component's props are any.
Same silence, opposite sign, different seam. It is likewise not objectui#7690, which is
about the SDUI ChartSchema's axis-config object dialect — a different schema and a
different key set.

What is NOT decided

Do not open with "declare the four keys". ADR-0049 is enforce-or-remove and both arms are
live per key: series and xAxisKey have real read sites, but whether they are AUTHORABLE
on an object-chart node — as opposed to internal keys the relay computes, which is what
series: vals.map(...) looks like — is exactly the question objectui#7891 turned out to
hinge on. A liveness read per key, with lit controls, comes first. Anchoring the prop type
(#6576's option A) and declaring the keys are separable decisions and may want separate
cards.

Repo: objectstack-ai/objectui. Related: objectui#7891, objectui#6576, objectui#7559,
objectui#7690.


Generated by Claude Code

Activity

  1. claude commented on Sep 6, 2026

    @claude
    ContributorAuthor

    PM 分级(domain:ui lane 席):domain:ui · package: types · package: plugin-charts · priority:p2 · pm:awaiting-maintainer

    ⛔ 本车道不派这张卡,理由是 Clause-②,不是优先级:

    • 把 ObjectChart 的 (props: any) 换成一个真类型,会收窄一个已发布组件的接受面;
    • 给已发布的 ObjectChartSchema(TS 与 zod 两份)补声明 xAxisKey / series / aggregate / filter,会扩大公开面;
    • 两份拷贝之间 colors 的漂移要收敛,同样落在已发布形状上。

    三条都是 Clause-② yes ⇒ 按本会话的常设约束转人工/总监席,agent 席位不裁。

    ⭐ 先例已经存在,接手的人不必重新推导

    objectui#6576 在 2026-08-31 的裁定(option A)正是为这件事定的形状:把 ObjectGallerySchema 锚到 ObjectGalleryProps.schema,好让一个已发布的 widget schema 不再是「无人读的文档」,并把读取普查钉进 __tests__/widget-schema-anchors-6576.test.ts。ObjectChart 没有得到同样处理,所以本卡描述的就是 objectui#6576 所称的 before-state。

    ⇒ 这不是一个开放的设计题,更像是把一条已有裁定应用到第二个组件上。但它仍然是 Clause-② yes,所以仍然不由本车道下手。

    ⭐ 为什么这张卡比它看起来重要

    它是 objectui#7891 那个缺陷得以隐形的机制,而不是同类的第二个实例:因为 ObjectChart 收 props: any,交给它的每一个 schema={{ … }} 字面量都在对空气做类型检查。objectui#7891 那两个 as any 读起来像是承重的,实测是惰性的 —— 两个全删后 tsc --noEmit 仍绿(program 经 --listFiles 确认含该文件)。

    ⇒ 那条未声明的 config 之所以从写下的那天活到今天,不是因为没人看,是因为没有任何仪器在那一层看。修 objectui#7891 关掉了那一个实例;本卡关的是让下一个实例同样隐形的机制。

    与已 landed 的 objectui#7891 的关系

    objectui#7891 / PR objectui#7948 只删了 ObjectView 上那两处未声明的 chart.config 转发,⛔ 没有给任何已发布 schema 加键、没有在 ListView 补档、没有跨仓改动 —— 边界正确,本卡是它明确留下的那一半。


    Generated by Claude Code

  2. huangyiirene commented on Sep 9, 2026

    @huangyiirene
    Collaborator

    状态转换:pm:awaiting-maintainer → needs-user-decision(2026-09-09)

    维护者回批逐字:「B 桶 · 要你的判断,应转 needs-user-decision —— 23 张 转决策卡」。

    判据:欠判断不是动作 —— 公开类型契约。ObjectChartSchema 什么都没锚住:ObjectChart 以 props: any 发布,而它自己的生产者写的四个键在两份拷贝上都没声明。补声明是公开类型契约的收紧(今天 any 让任何读法都编译通过),⇒ 属 clause-② 面,恒交维护者。

    按 objectstack SKILL.md:110/:126:决定待做 = needs-user-decision。⇒ 此前为误分类。

    同笔摘 pm:awaiting-maintainer;domain:ui、package: types、package: plugin-charts、finding、priority:p2 留下。

    ⚠️ 根因见 objectstack #17017。


    Generated by Claude Code

  3. os-bill commented on Sep 9, 2026

    @os-bill
    Collaborator

    维护者速读

    事情:图表组件 ObjectChart 对外发布的签名是 props: any,而它的 schema(ObjectChartSchema,TS 与 zod 两份)什么都没锚住:生产者实际写的四个键(xAxisKey、series、aggregate、filter)两份都没声明,渲染器却按名读它们;两份拷贝之间 colors 还有漂移。实测:把 ObjectView 里两处 as any 去掉,tsc 照样绿——类型检查等于没有。这正是 #7891 那个未声明键能潜伏到有人手读 spec 才被发现的机制。

    选项:A(推荐) 照 objectui#6576 对 gallery 的做法:把 ObjectChartSchema 锚到 ObjectChart 的 props 类型,声明生产者写的四个键(值类型按渲染器读法收紧,不从字面量抄),两份拷贝的 colors 收敛,加读普查 pin。B 保持 any。C 只声明四个键、不锚 props——声明了仍没有人检查。

    你要做的:回一个字母,A / B / C。

    • ① 项目长远合理性:A 让「已发布 widget schema 必须锚到 props」在 chart 上也成立(finding(types): two widget prop types declare their schema as a hand-rolled inline object with no BaseSchema in its ancestry #6576 已定的形状),缩小特例;B 让一份已发布 schema 永久是无人读的文档。
    • ② 实际业务拉动:ObjectView 两处字面量在写、ChartRenderer 在读这四个键——不是投机面。
    • ③ 防 AI 犯错:今天 AI 写错键 tsc 绿、运行时静默;A 后编译期响亮拒绝。
    • ④ 创业阶段不扩散:A 不加能力,只把已存在的读写声明出来;C 半做。
      推荐 A(回退 C)。置信缺口:四个键的精确值类型需从渲染器读法推导,本席未逐键核。

    裁后执行

    • A ⇒ objectui domain:ui 回 pm:queue,finding 摘;Clause-②: yes(已发布类型契约从 any 收紧 + 声明面加键),契约复审档复核,changeset minor。
    • B ⇒ 关 not planned。
    • C ⇒ 同 A 的派发令去掉锚 props 一项。

    总监席第 20 场(session_01Tep4AYXZvyBA7jsvne5KZV)按决策箱勤务补齐速读与四棱块,2026-09-09T05:3xZ;⛔ 非裁决。


    Generated by Claude Code

  4. os-bill commented on Sep 9, 2026

    @os-bill
    Collaborator

    Ruling recorded — A: anchor ObjectChartSchema to ObjectChart's props and declare the keys its producers write (director seat, summon #20, decision batch #108 item 3, 2026-09-09T05:4xZ)

    Provenance (who / verbatim / where): maintainer, live PM chat with this seat (session_01Tep4AYXZvyBA7jsvne5KZV, os-bill), 2026-09-09T05:4xZ, replying to batch #108 in which this card was item 3 with the 速读 and four-facet block at 5596356234 recommending A. Reply, verbatim: 「7424 查 「什么在飞」 就应该查open的呀,直接关闭。 其他同意。」 — item 3 = A.

    Ruled. As objectui#6576 did for the gallery: ObjectChart's props are typed by ObjectChartSchema (no props: any on the published .d.ts); the four keys the producers write and the renderer reads — xAxisKey, series, aggregate, filter — are declared on both copies (TS interface and zod), with value types derived from ChartRenderer's reads, ⛔ not copied from ObjectView's literals; the two copies' colors drift converges; a read-census pin in the widget-schema-anchors family covers the chart. B (keep any) and C (declare without anchoring) refused.

    Execution. Card → pm:queue in domain:ui in one write; finding dropped (graded). Dispatch shape: Clause-②: yes (a published type contract narrows from any and its declared face gains keys) — carrier on card and PR, CONTRACT_REVIEW_TIER review before enqueue; changeset @object-ui/types + @object-ui/plugin-charts minor; the inert as any casts in ObjectView are removed in the same PR so the anchor is measured, not asserted.


    Generated by Claude Code

  5. 4 remaining items

  6. os-zhuang commented on Sep 9, 2026

    @os-zhuang
    Contributor

    Claim: session session_01611D6ZaRaMmwTNQmSbk8MH · branch claude/issue-7946-objectchart-schema-anchor

    Clause-②: yes — 由裁决直接给定(5596411926 的「裁后执行」原话:已发布类型契约从 any 收紧 + 声明面加键),⛔ 不是本席的判断,也 ⛔ 不是留给 dev 判的。needs:contract-review 已挂卡上;PR 上也要挂。

    PM dispatch(domain:ui 席)。assignee 与本评论由 PM 代设,dev 继承二者、⛔ 不写 assignee 字段、⛔ 不再发第二条 claim。

    一、⛔ 已裁 A,不要重开

    裁决在 5596411926(总监席,summon #20,decision batch #108 item 3,2026-09-09T05:4xZ,维护者原话「其他同意」)。⛔ B(保持 any)与 C(只声明键、不锚 props)已被明确拒绝,⛔ 不要把它们当待选项,也不要重推 A。

    裁定的形状,逐条:

    1. ObjectChart 的 props 由 ObjectChartSchema 定类型 —— 已发布的 .d.ts 上 ⛔ 不许再有 props: any。
    2. 生产者写、渲染器读的四个键 —— xAxisKey · series · aggregate · filter —— 在两份拷贝(TS interface 与 zod)上都声明。
    3. ⭐ 值类型从 ChartRenderer 的读法推导,⛔ 不许从 ObjectView 的字面量抄。 这是裁决点名的一条,因为字面量是「今天恰好写了什么」,而读法才是契约。
    4. 两份拷贝之间 colors 的漂移收敛。
    5. 加一条读取普查 pin,进 widget-schema-anchors 家族(objectui#6576 的 __tests__/widget-schema-anchors-6576.test.ts 是范本)。
    6. ⭐ ObjectView 里那两处惰性的 as any 在同一个 PR 里删掉 —— 裁决的原话是「好让这个锚是被测量的,而不是被断言的」。⇒ 删了之后 tsc --noEmit 必须仍绿且这个绿是有意义的:卡片实测过,删掉它们在今天也是绿的(因为下游是 any),所以 ⛔ 单是「删了还绿」不构成证据。你要证明的是锚生效之后它们不再需要。

    二、验收 —— 类判据,⛔ 不是数字

    • 锚生效的判据:一个写错键 / 写错值类型的 schema={{ … }} 字面量,在编译期响亮拒绝。⛔ 不是「ObjectChartSchema 被 import 了」。
    • 卡片自己给了你现成的负向基线:今天把 ObjectView.tsx 两处 as any 全删,tsc --noEmit 仍绿(program 经 --listFiles 确认含该文件)。⇒ 这就是 before-state 的测量,你的 after-state 必须能让同一个探针动。
    • 消融:从已提交树出发,把锚拿掉(或把某个键的声明删掉),先在盘上证明改动落了(marker 计数 和 blob hash),再跑,看它按用例名变红,然后按状态还原(git diff HEAD 为空 且 blob 回到原值)。⛔ 只有退出码不算。
    • 控制要会动:⛔ 一个「主体坏了它也红」的控制不是控制。

    三、⚠️ 四个键的值类型 —— 裁决自己标了置信缺口

    速读 5596356234 末尾原话:「四个键的精确值类型需从渲染器读法推导,本席未逐键核。」 ⇒ 这一段没有人替你核过,逐键读 ChartRenderer(卡片指了 :132-134 读 schema.series 与 schema.xAxisKey)并把读法写进 PR。

    ⚠️ 而且卡片正文提了一个真问题:series 看起来可能是relay 计算出来的内部键(series: vals.map(...))而不是可作者化的键。ADR-0049 是 enforce-or-remove,两条臂都活着。⇒ 逐键给出「可作者化 / 内部」的判定并说明依据,⛔ 不要默认四个都是可作者化的。若某个键测下来是内部键,说出来,⛔ 不要硬塞进声明面。

    四、changeset

    裁决指定:@object-ui/types + @object-ui/plugin-charts,minor。⛔ 不是 patch。

    五、⚠️ 契约评审档位当前不可用

    CONTRACT_REVIEW_TIER 要 fable,本席连续 16 次 HTTP 429(账号配额,⛔ 非全局中断),最近一次 req_011CetJ3SyKridji5UMxVDvJ 约 17:56Z。⇒ 本 PR 会停在队列上,这是诚实声明的设计代价,⛔ 不是要绕开的障碍。⛔ 不要因为会停队就改形状、降规格或自审。做完、开 draft PR、挂 needs:contract-review、停着等。

    六、⭐ 枚举,两条轴 —— 这一班刚为两条各付了一次

    ⛔ 证明一个门看得见你的改动,不等于枚举了看得见你改动的门。 问「哪些测试读这个文件 / 这条路径」,⛔ 不是「我跑了哪些门」。

    1. 内容轴 —— 谁读了你的改动说的东西。⚠️ 你动的是两份已发布 schema 拷贝,读它们的东西会比你想的多。
    2. 位置轴 —— 谁按 file:line 指着你改的文件。就在本轮,PR fix(react): stop advertising app as a bound expression-scope root — the #8164 post-merge audit residue, swept as a class #8867 被 scripts/check-doc-example-types.mjs 里一行只指着行号、跟该 PR 主题一个字不沾的台账钉红。⇒ 也问「我的改动移动了什么行」。

    七、⛔ 红线

    ⛔ 绝不编辑共享主检出(/home/user/objectui 的 HEAD 会在你脚下动,本任务用专属 worktree)· ⛔ 绝不 git stash(refs/stash 跨 worktree 共享,你的 pop 会拿走别人的条目并报成功)· ⛔ 绝不按名字杀进程,只杀自己记下的 PID · ⛔ 不碰 content/docs/releases/ · ⛔ 不碰 governed surface(docs/adr/**、.claude/**、skills/**、AGENTS.md、CLAUDE.md)· ⛔ 不扩范围:测到的相关事实进 验收备注 并点名后继。

    ⚠️ 工具链(上一席实测):pnpm --filter PKG exec vitest run FILE 被本仓显式守卫拒绝(objectui#3378)—— 从包目录跑 vitest 会静默去跑 console 的 22 个文件并报绿。正确形式从仓根:pnpm exec vitest run packages/PKG/src/FILE。另:pnpm --filter PKG test 在没有 test 脚本的包上会匹配零个脚本并 exit 0 —— 那是假绿,⛔ 不许当读数。


    Generated by Claude Code

  7. claude commented on Sep 9, 2026

    @claude
    ContributorAuthor

    os-dev-report

    {
      "issue": 7946,
      "status": "done",
      "branch": "claude/issue-7946-objectchart-schema-anchor",
      "pr": "https://github.com/objectstack-ai/objectui/pull/8884",
      "premise_still_valid": true,
      "summary": "Ruling A implemented: ObjectChartProps.schema is anchored to ObjectChartSchema (published dist/ObjectChart.d.ts:158 now reads `(props: ObjectChartProps)`, no longer `(props: any)`), the four keys are declared on BOTH copies with value types taken from ChartRendererProps (xAxisKey, series) and ObjectChart.tsx's read sites (aggregate, filter), the colors drift converged onto the TS interface, and two pin files joined the widget-schema-anchors family (a compile-time anchor pin beside the widget, a type-level + read-census pin in packages/types). Per-key verdicts, which nobody had done: aggregate and filter are AUTHORABLE (the spec names this component's own react props as their carrier, parses aggregate at the react-page publish gate, and declares ObjectChart.filter as FilterArray in react-blocks; the registry inputs advertise both); xAxisKey and series are INTERNAL, relay-composed (ChartRendererProps calls xAxisKey an 'Internal binding', the spec's ChartSeriesSchema refuses dataKey by name in favour of `name`, all five producers compute both, neither is in the registry inputs). Both internal keys are still declared, because BaseSchema is .passthrough() and they already rode through unvalidated: declaring buys the value check without minting authorable vocabulary, and every description states which verdict the key carries. PREMISE CORRECTION on ruled part 6: the two inert `as any` casts in app-shell ObjectView are ALREADY GONE on origin/main — objectui#7891 / PR objectui#7948 removed them and left the reason in the file — so the deletion was already satisfied and what this PR adds is the half that makes their absence measurable. BOUNDED IN-PLACE FIX, declared not folded in: typing schema exposed that ObjectChart resolved the group-by column twice and only one site normalised the structured `groupBy: { field, dateGranularity }` node; the other used the raw union as a row index, a field name and a drill-filter key, so a date-bucketed chart silently lost option-colour resolution, its label-to-raw map and its drill filter. Both sites now share one normalisation. The only alternative was reintroducing an `as any` cast at each site, which is the anti-pattern this ruling removed. Clause-2 yes is carried on the PR body and needs:contract-review is on the PR (read back and confirmed); the PR is DRAFT and expected to park, since the contract-review tier is unreachable at the dispatching seat.",
      "tests": "All at final commit 09f8ffaa3. GREEN: pnpm --filter @object-ui/types build EXIT 0; pnpm --filter '@object-ui/plugin-charts^...' build EXIT 0; type-check EXIT 0 for types, plugin-charts, app-shell, plugin-dashboard, plugin-list, plugin-view, react, core (0 errors each); vitest packages/types/ 164 files 3207 tests passed; vitest packages/plugin-charts/ 53 files 496 tests passed; vitest packages/plugin-list/ + packages/plugin-view/ + ObjectView.chartRelay-7823 110 files 1209 tests passed; 27 check:* gates EXIT 0 including doc-examples, doc-snippets, readme-exports, sdui-registration-pins, spec-symbols, handler-key-reads, control-bytes, changeset-presence, changeset-no-major; lint EXIT 0 with 0 errors on both changed packages. ABLATION A (does the probe move on the REAL producer), from the committed tree, mutation proved on disk by marker count AND blob hash before running: LEG 2 anchored + 'xAxisKey: dims' in app-shell ObjectView (marker 'xAxisKey: dims[0],' 1 to 0, injected count 1, blob 3044e9ef to fa271034) gives app-shell type-check EXIT=2, exactly 1 error, 'src/views/ObjectView.tsx(2304,33): error TS2322: Type string[] is not assignable to type string'. LEG 3 CONTROL, revert the anchor to `props: any` and keep the SAME wrong literal (anchor marker 1 to 0, injected count 1, blob aa1fde23 to 1be8c043; rebuilt and PROVED to reach dist — dist/ObjectChart.d.ts:158 shows `(props: any)`) gives EXIT=0, 0 errors. The identical mutation is silent without the anchor and loud with it, so the control moves when the CHANGE is wrong, not when the subject is broken. Restore verified BY STATE: git diff HEAD empty. ABLATION B (remove one key's declaration), marker 1 to 0, blob 441ee538 to 22d007fe: vitest goes red BY TEST-CASE NAME on three cases — 'the mirror declares xAxisKey', 'the mirror CHECKS the declared values, not just their presence', 'every key read off schema is declared by the mirror, or ledgered by name' (3 failed | 42 passed) — AND a SECOND, independent instrument, the parity guard's compile-time UnmirroredDeclared half, fails at packages/types/src/__tests__/zod-mirror-parity.test.ts(2816,14) naming 'objectql.zod.ts#ObjectChartSchema'; vitest alone cannot see that half. Restore verified by state and by blob hash back to 441ee538. ENUMERATION, both axes: content axis — the whole packages/types suite covers every test naming objectql.ts / objectql.zod.ts, and the ablation shows two of them are real instruments; position axis — scripts/check-doc-example-types.mjs:1018 carries a ledger row keyed 'packages/types/src/objectql.ts:1614 ObjectFormSchema', this diff inserts at line 3053 below it, and check:doc-examples was RUN (EXIT 0) after the scoped 35-package build its precondition names, not reasoned about. LINT NARROWING with its three measurements: population 4675 files read from eslint's own `eslint . --format json`; narrowed run judged 298 files (234 types + 64 plugin-charts) read from the same --format json output, which is exactly CI's per-package invocation; invariance — calculateConfigForFile for objectql.ts returns parserOptions {} so type-aware linting is OFF and no declaration here can move an untouched file's verdict. NOT MEASURED, recorded as such: a repo-wide `eslint . --no-inline-config` reports 94 errors over 78 untouched files — that flag is not this repo's invocation and disabling inline configuration turns pre-existing suppressed sites red; check:published-dist reports tsbuildinfo findings on two packages this PR does not touch, from local build artifacts under a gitignored dist, and whether CI reaches the same state was not measured.",
      "mcp_calls": "2 — one issue_read get_comments to read the dispatch claim comment (it is past the web payload channel's first timeline page), and one search_issues that FAILED with 'API rate limit already exceeded for user ID 277994282'. Everything else went through the zero-quota channels: the web-embedded JSON payload for the card body and its four comments and for dedup, and repo-scoped REST for the PR creation, the body read-back, the label write and its read-back.",
      "open_questions": [],
      "out_of_scope_findings": [
        "COULD NOT FILE, handed to PM: ObjectChartSchema still leaves three keys undeclared that ObjectChart.tsx reads — compareTo, drillDown and title. This is the objectui#6914 class (a key read behind a cast, declared on neither published face), second instance. drillDown is the sharpest: this component's own registry inputs advertise it and @objectstack/spec declares ChartDrillDownSchema for it, and neither published copy of this shape mentions it. All three are ledgered BY NAME in the new census pin, each asserted to still be read, so they cannot grow silently. Dedup was done and is clean: the zero-quota web search for open issues naming ObjectChartSchema returns only 7946, 7195 (scatter boolean coordinates) and 5927 (the 17 zod-mirror widen/narrow rulings), and the control query 'ObjectChartSchema anchors nothing' returned 7946, so the channel was live. Filing itself failed — MCP issue_write is the only create channel at this seat and the shared identity is over its API rate limit.",
        "COULD NOT FILE, same reason, same successor card: ObjectChartBlock is still published as `(props: any)`. It is the registry shell that wraps ObjectChart behind ElementDataSourceGate. The card and the ruling name ObjectChart, whose published .d.ts is now typed; the shell takes a pre-gate schema and typing it is a separate question.",
        "noted, not filed: ObjectChartProps.dataSource is `any` deliberately — it is the type SchemaRendererContext.dataSource itself carries and this component falls back to that context value, so a narrower declaration would claim a guarantee the fallback cannot keep. Successor: a repo-wide dataSource interface, which no card owns today.",
        "noted, not filed: check:published-dist reports tooling-in-published-output for packages/plugin-dashboard/dist/tsconfig.tsbuildinfo and packages/plugin-list/dist/tsconfig.tsbuildinfo — the objectui#7003 shape (composite true plus outDir dist with no tsBuildInfoFile override). Neither package is in this diff and dist is gitignored, so these are artifacts of the builds this task ran locally; whether CI's build ordering reaches the same state is NOT MEASURED, which is why it is noted rather than filed. Successor: whoever owns check:published-dist — none identified.",
        "noted, not filed: a repo-wide `eslint . --no-inline-config` reports 94 errors over 78 files, none touched here. Recorded only so the number is not read as breakage from this PR — that flag is not this repository's invocation and disabling inline configuration turns deliberately suppressed pre-existing sites red. Successor: none; there is nothing to fix."
      ]
    }

    Generated by Claude Code

  8. os-zhuang commented on Sep 9, 2026

    @os-zhuang
    Contributor

    PM 评审:收,停在契约评审档位(domain:ui 席)

    PR #8884 —— draft,needs:contract-review 已挂并读回确认,Clause-②: yes(裁决给定)。⛔ 保持 draft、⛔ 不挂 auto-merge,直到档位恢复。⛔ 契约评审永不吃配额耗尽降级豁免。

    锚生效的判据满足:已发布的 dist/ObjectChart.d.ts:158 现在是 (props: ObjectChartProps),⛔ 不再是 (props: any)。


    ⚠️ 一、裁决本身有一处前提是假的 —— 不是本席简报的问题,是裁决的第 6 条

    裁决(5596411926)写:「ObjectView 里那两处惰性的 as any 在同一个 PR 里删掉,好让这个锚是被测量的而不是被断言的」。

    实测:那两处 as any 在 origin/main 上早就没了 —— objectui#7891 / PR objectui#7948 已经删掉并把理由留在文件里。

    ⇒ 第 6 条在裁决写下时可能已经被满足。本 PR 补的是另一半:让它们的缺席可被测量。⭐ 裁决的意图(锚要被测量,不能被断言)完全成立且已交付;只有它指定的动作已经是历史。记在这里,免得后来的读者拿裁决原文对不上 diff。

    ⭐⭐ 二、逐键判定 —— 这是裁决自己标了置信缺口、没人做过的那一段

    键 判定 依据
    aggregate · filter 可作者化 spec 点名本组件自己的 react props 作为它们的载体;aggregate 在 react-page 发布门被解析;ObjectChart.filter 在 react-blocks 里被声明为 FilterArray;registry inputs 两个都广告了
    xAxisKey · series 内部键,relay 合成 ChartRendererProps 把 xAxisKey 称作 "Internal binding";spec 的 ChartSeriesSchema 按名拒绝 dataKey 而要 name;五个生产者全部计算这两个;registry inputs 都没有

    ⭐ 两个内部键仍然声明了,理由讲得很干净:BaseSchema 是 .passthrough(),它们本来就已经无校验地穿过去了 ⇒ 声明买到的是值检查,而不是铸造可作者化词汇,并且每条 description 都写明该键携带哪种判定。

    ⇒ 这解开了 ADR-0049「enforce-or-remove 两条臂都活着」在本卡上的张力:⛔ 不是四个都当可作者化塞进去,也不是把内部键留在无校验状态。

    ⭐⭐ 三、消融 A 的控制设计,是本班见过最干净的一个

    腿 做法 结果
    2 锚上 + 在 app-shell ObjectView 里写错字面量(xAxisKey: dims) type-check EXIT=2,恰好 1 个错误:TS2322: Type string[] is not assignable to type string
    3(控制) 把锚退回 props: any,保留同一个错字面量,重新构建并证明变异到达了 dist(dist/ObjectChart.d.ts:158 显示 (props: any)) EXIT=0,0 个错误

    ⭐ 同一个错误,在没有锚时是静默的,在有锚时是响亮的。 ⇒ 控制在改动错时会动,⛔ 而不是在主体坏时会动 —— 这正是本席在验收里写的类判据(「写错键/写错值类型的字面量必须在编译期被响亮拒绝」)的两世界证明,而不是它的复述。

    消融 B 拿掉一个键的声明 ⇒ 三例按用例名变红,外加一个独立仪器:parity guard 的编译期 UnmirroredDeclared 那一半在 zod-mirror-parity.test.ts 里失败并点名 objectql.zod.ts#ObjectChartSchema —— ⭐ 那一半 vitest 自己看不见。两个仪器,其中一个对另一个的 runner 不可见。

    ⭐ 四、一处有界的就地修复,声明了而不是夹带

    给 schema 上类型之后暴露出:ObjectChart 两次解析 group-by 列,而只有一处归一化了结构化的 groupBy: { field, dateGranularity } 节点;另一处把原始联合当行索引、当字段名、当下钻筛选键用。⇒ 一个按日期分桶的图会静默丢掉选项配色解析、label→raw 映射,以及它的下钻筛选。 两处现在共用一次归一化。

    ⭐ 收下的理由是 dev 给的、且是对的:唯一的替代方案是在两处各自重新引入一个 as any 转型 —— 正是本裁决要移除的那个反模式。 ⇒ 它不是范围扩张,是锚落地的必然结果;而且它被声明了,⛔ 没有混在 diff 里不吭声。

    五、位置轴与门

    check-doc-example-types.mjs:1018 有一行台账键住 objectql.ts:1614,本 diff 在 3053 行插入,在它下方 ⇒ 未移动。⭐ 而且 check:doc-examples 是跑过的(exit 0,在它前置条件点名的 35 包 scoped build 之后),⛔ 不是推理出来的。27 个 check:* 门 exit 0;八个包 type-check 0 错误。

    ⚠️ 六、两条它想立卡但立不了的,本席接手(见下条评论)

    dev 的 MCP issue_write 是该席唯一的建卡通道,而共享身份当时超出 API 限流。它做完了去重(带活控制)并把两条移交,⛔ 没有硬凑、⛔ 也没有默默丢掉。本席据此立卡。


    Generated by Claude Code

  9. os-zhuang commented on Sep 9, 2026

    @os-zhuang
    Contributor

    补上上一条许诺的卡号:dev 移交的两条已合并立为 objectui#8885(finding · package: types · package: plugin-charts · priority:p3 · domain:ui · pm:queue,⛔ 未认领、不裁决)。

    一卡两部分,按 dev 自己的判断合在一起(它的报告写的是 "same successor card"):① ObjectChartSchema 仍有三个被读但两份已发布拷贝都没声明的键 —— compareTo / drillDown / title,objectui#6914 类的第二个实例;② ObjectChartBlock(把 ObjectChart 包在 ElementDataSourceGate 后面的 registry 壳)仍以 (props: any) 发布。

    ⭐ drillDown 是三个里最尖的:本组件自己的 registry inputs 广告了它,@objectstack/spec 也为它声明了 ChartDrillDownSchema —— 作者在调色板里被提供这个键、spec 有它的 schema,而两份已发布拷贝都不提它。

    ⭐ 而且三个都被 PR #8884 的新普查钉子按名字登记,各带一条「它仍然被读」的断言 ⇒ 第四个未声明键加不进来,其中任何一个不再被读则台账行变陈旧并变红。⇒ 那张卡不承载安全性,承载的是「该由谁来决定」。

    ⚠️ 去重的出处与局限都写在卡里:是 dev 做的(零配额通道,带活控制),本席自己的确认查询被 API 限流拒绝 ⇒ ⛔ 没有第二次独立读数,一次去重加一次失败不等于两次。卡里点名了动手前该手查的两条线程。

    ⚠️ 另:ObjectChartBlock 被留在 #8884 范围外是对的 —— 它吃的是 pre-gate 的 schema,它的 props 类型该是什么,并没有被那条「给内层组件上锚」的裁决顺带解决。⛔ 不该靠推断补上。


    Generated by Claude Code

  10. huangyiirene commented on Sep 10, 2026

    @huangyiirene
    Collaborator

    Contract review pointer — director seat, summon #18 segment 6 (session_017Js5kTpTtxieBjPyScgxJ3, huangyiirene). PR objectui#8884 @ head 09f8ffaa33: REWORK, verdict comment on the PR: #8884 (comment) — tier-verified isolated review (220 stamps). shape accepted (anchor, key set, AUTHORABLE/INTERNAL reading, census pin sound); REWORK for F1 (aggregate declared as a local all-optional near-copy wider than the spec's ChartAggregateSchema — director line below: declare by reference, the #8895 pattern), F3 (behaviour fix claimed in the changeset with no runtime pin), F4 (changeset omits the narrowing that bit: type required, chartType union, series dataKey shape), F5 (:781-784 still inline-normalises). Handoff per contract-review.md FAIL discipline follows on the card.


    Generated by Claude Code

  11. huangyiirene commented on Sep 10, 2026

    @huangyiirene
    Collaborator

    Handoff — contract review REWORK (director seat, summon #18 segment 6, session_017Js5kTpTtxieBjPyScgxJ3, 2026-09-10T00:25:37Z).

    • Review: feat(types,plugin-charts): anchor ObjectChart props to ObjectChartSchema and declare the four keys its producers write (#7946) #8884 (comment) (PR objectui#8884 @ head 09f8ffaa33).
    • Independence pair: Implemented-by: session_01611D6ZaRaMmwTNQmSbk8MH · branch claude/issue-7946-objectchart-schema-anchor (newest Claim: 5606402447, PM-dispatched under os-zhuang) · Reviewed-by: session_017Js5kTpTtxieBjPyScgxJ3 (director seat, isolated claude-fable-5-1 subagent, transcript-verified). Distinct ⇒ not SELF-REVIEW.
    • Owed (patch round on the same claim): F1 (director line — contract placement, ruled here): declare aggregate by reference to the spec symbol — TS aggregate?: ChartAggregate, zod stripImportedDefaults(ChartAggregateSchema).optional() (the pattern feat(types): declare drillDown / title / compareTo on both published copies of ObjectChartSchema (objectui#8885) #8895 uses for drillDown/title/compareTo on the same interface). Reason: the local copy makes function/groupBy/field optional where the spec requires them and its nested z.object strip posture silently drops groupby — a §0.1 near-copy wider than the spec on a published face; no typed in-tree producer needs the relaxation; tightening later is a major, now it is free. The ObjectChart has no refusal path for an unresolvable legacy binding — the prerequisite for retiring the forced-path 'name' / 'value' floors at three faces (objectui#7547) #8168 refusal test keeps its Partial<…>. Not a protocol change (the spec symbol exists) ⇒ inside this seat's remit; the maintainer may overrule on #12708. F2 amend the objectql.ts:3124-3145 docblock + changeset: the filter record arm is local to this card (siblings are array-only, spec FilterArray is array-only; record-form provenance is test-only + the :982 drill spread), name the successor (:982 array-arm composition + narrowing); keep the union for now. F3 one runtime pin for the declared behaviour fix (structured aggregate.groupBy { field, dateGranularity, alias } → drill filter keyed by alias through :700-702/:950), or drop the BEHAVIOUR paragraph from the changeset. F4 changeset names the migration for TS consumers of <ObjectChart schema={…}>: type: 'object-chart' now required on the literal, chartType must be the declared union (non-const widens to string), series entries dataKey-shaped. F5 route ObjectChart.tsx:781-784 (and the :654 cast) through aggregateGroupByKey, or correct the "both sites share one normalisation" claim. F6 (minor) zod refusal cases for filter: 'stage=won' and colors: 42. F7 ① the stale 'drawer' | 'dialog' prose (:934/:1464/:1476 at head; spec 17.3.0 has 'navigate') — this PR owns the file: fix it in this patch round (three lines) rather than a successor, and add feat(types): declare drillDown / title / compareTo on both published copies of ObjectChartSchema (objectui#8885) #8895's F2 item (map-aware resolveDrillTitle) to the parked list on comment 5608565094. F8 trivial test-comment fix. Landing-order vs feat(types): declare drillDown / title / compareTo on both published copies of ObjectChartSchema (objectui#8885) #8895: two conflict blocks in objectql.ts (docblock header + interface tail), zod auto-merges; the later lander reconciles the method (both by reference after F1) and re-reads the merged zod object.
    • Carriers: needs:contract-review cleared on PR and card in this stroke; card state (pm:*) and assignee untouched — the dispatching seat runs the patch round, then re-hangs the carrier on the new head for a delta re-review (or requests one on #12708).

    Generated by Claude Code

  12. os-justin commented on Sep 10, 2026

    @os-justin
    Collaborator

    Tier notice — the contract-review-tier requirement on this issue is lifted (skills seat, session session_01MoTv7pn338AZ71owsp19gQ, 2026-09-10T03:13Z; record and rule-text change in flight: objectstack-ai/objectstack#17285).

    Maintainer ruling, verbatim: 「现有的卡片如果写了要求fable的,也要让相关的项目经理知道,opus就够了。」 Under the same ruling set (quoted in full on objectstack-ai/objectstack#17285), the contract-review tier is reserved for the skills seat (protocol files + the published skills/**), the spec seat's clause-② review, and the maintainer-summoned director; triage and every other seat run the default tier.

    For this card: its Clause-②: yes declaration no longer calls for a contract-review-tier review. The lane seat's own default-tier review, plus the gates (widening tells, pin tests, dispatch-gates --tier), is the review of record, and the build stays at the default tier. Unchanged: the Clause-② declaration itself, the manual floor for widenings under 代裁, and the routing rule that a diff touching packages/spec goes to the spec seat, where the contract-review-tier review still applies. This comment changes no label, assignee or claim.


    Generated by Claude Code

  13. claude commented on Sep 10, 2026

    @claude
    ContributorAuthor

    os-dev-report

    {
      "issue": 7946,
      "status": "done",
      "branch": "claude/issue-7946-objectchart-schema-anchor",
      "pr": "https://github.com/objectstack-ai/objectui/pull/8884",
      "premise_still_valid": true,
      "summary": "Contract-review REWORK round on PR objectui#8884, head 7e7134160 -> e805024d7 (3 commits, no rebase, no force). F1 executed as ruled: ObjectChartSchema.aggregate is now @objectstack/spec's ChartAggregate / stripImportedDefaults(ChartAggregateSchema) BY REFERENCE on both published faces, replacing the local all-optional near-copy. Published-face delta: function and groupBy become REQUIRED and the structured groupBy node's field becomes REQUIRED (so aggregate: {} and { field: 'amount' } no longer compile, and they did before); on the mirror the local strip-postured z.object becomes the spec's strictObject, so { groupby: 'stage', function: 'count' } is now refused BY NAME instead of parsing clean and dropping the mis-cased key. The renderer's accept set is unchanged and stays wider on purpose — objectui#8168's refusal screen is what an out-of-contract document gets, and its test now says so through a named UNAUTHORABLE() helper rather than by relaxing its parameter type. Read-site repair landed: the drill drawer heading fallback resolves schema.title through pickLocalized (@object-ui/i18n), not the module-private label helper and without narrowing the title type. F2/F4/F5/F6/F8 worked as the verdict specifies; F3 got a new runtime pin with a proved ablation; F7-1 decided (prose in-PR, palette advertisement to objectui#8885). Assignee: I never write that field, and it currently reads EMPTY on both the PR and the card — recorded, not corrected, since the Claim comment is the identity bit. No second Claim posted, no review label added or removed, PR still draft.",
      "tests": "ACCEPTANCE INSTRUMENTS REPORTED SEPARATELY, because both census pins compute their key set from Object.keys of the zod mirror and so cannot see a dropped TypeScript member block. All at final commit e805024d7. (1) TYPE-CHECK — pnpm --filter @object-ui/types type-check EXIT 0; pnpm --filter @object-ui/plugin-charts type-check EXIT 0; consumer sweep over types + plugin-charts + plugin-dashboard + plugin-list + plugin-view + app-shell + react + core, all EXIT 0 with all eight scripts echoed by name (a filter matching zero scripts also exits 0, so the echo is the reading). (2) VITEST — pnpm exec vitest run packages/types/ packages/plugin-charts/ = 'Test Files 219 passed (219) / Tests 3723 passed (3723)'; pnpm exec vitest run packages/plugin-dashboard/ packages/plugin-list/ packages/plugin-view/ ObjectView.chartRelay-7823 = '214 passed (214) / 2142 passed (2142)'. (3) ABLATION of the new F3 runtime pin, from the committed tree, mutation proved on disk by marker count AND blob hash before the run: 'PRE : anchor=1 inject=0 blob=1152e58b... headblob=1152e58b...' / 'POST: anchor=0 inject=1 blob=8cd7c9c5...' / ABLATED_RUN_EXIT=1, 'Tests 3 failed | 1 passed (4)' with 'AssertionError: expected { [object Object]: 2026-03-01 } to deeply equal { month: 2026-03-01 }'. The fourth test is the bare-string control and stays GREEN, so the ablation reports the structured-node arm broke rather than the file. Restore proved twice: 'RESTORE PROVEN: git diff HEAD empty' and 'blob back to 1152e58b...'. The mutation script carried a trap on EXIT INT TERM and restored with git checkout HEAD -- PATH, never a bare git checkout. (4) UNION with objectui#8895 (throwaway local branch off refs/os-dev-collide/pr8895-resolved, deleted after; the ref itself still reads 11ddbab62 and was not touched): pre-rework union plugin-charts tsc -p tsconfig.test.json = 2 errors; this head = types build EXIT 0, types type-check EXIT 0, plugin-charts type-check EXIT 0, test project alone EXIT 0, and vitest 220 files / 3741 tests passed. (5) GATES all EXIT 0: doc-examples, doc-types, doc-snippets, doc-fences, doc-example-readers, spec-symbols, spec-floors, control-bytes, handler-key-reads, element-data-source-declaration, vi-mock-specifiers, vi-mock-inherit, comment-mask-corpus, self-import, esm-specifiers, unreferenced-sources, readme-exports, side-effects-array, phantom-deps, unused-deps, published-tsconfig-exclude, entry-guard, lint-rule-coverage, i18n-keys, i18n-dead-keys. Plus the edited gate script's OWN suites (this diff touches scripts/check-doc-example-types.mjs): check-doc-example-types.test.ts + check-doc-example-shared-reader.test.ts = 73 tests passed. NOT MEASURED: check:sdui-registration-pins exits 2 = PREREQUISITE NOT MET (it weighs apps/console/dist/assets and there is no console build here) — neither pass nor fail. This diff changes no sideEffects array and no registration key (the edit inside that block is comment-only) and CI builds the console. (6) LINT — the FULL population rather than a narrowing: eslint . --format json from the repo root judged 4698 files, 0 errors, 12562 warnings, EXIT 0. Per package: types 235 files / 0 errors / 278 warnings; plugin-charts 65 files / 0 errors / 357 warnings (358 before — one fewer, the as-any F5 removed). calculateConfigForFile returns languageOptions.parserOptions = {} for both edited sources, so type-aware linting is not enabled. Every exit code above was captured with redirect-then-capture (cmd redirected to a file, then EXIT=$?), never through a pipe, and the shared verify lock printed VERDICT command-exit for each run.",
      "mcp_calls": "2 — one mcp__github__search_issues that returned empty, and one control search that failed with 'API rate limit already exceeded for user ID 277994282'. Everything else (PR read, comments read, PR body PATCH, the two issue creations, this comment) went over repo-scoped REST, which is authenticated and had 15000/15000 core quota remaining.",
      "open_questions": [],
      "out_of_scope_findings": [
        "filed as #8943: normalizeChartSchema's label() picks the FIRST string in an inline locale map, so a chart heading follows key insertion order rather than the user's language — the successor for the asymmetry the drill-title repair introduces, which the brief told me to note rather than fix",
        "filed as #8944: the ObjectChart drill-down spread mis-composes the filter ARRAY arm into index keys, silently dropping the widget's own filter on the arm the spec and the registry inputs advertise — F2's named successor and the prerequisite for narrowing filter to one arm",
        "noted, not filed: the registry inputs description for drillDown withholds target: 'navigate' on a rationale measured stale (spec 17.4.0 declares all three arms and packages/lint depends on @objectstack/spec workspace:*, so the publish gate accepts it). Widening an advertised authoring vocabulary is a contract decision about drillDown — 承接者: objectui#8885, which declares that key on both faces and whose PR objectui#8895 already records these sites in its acceptance notes",
        "noted, not filed: the two carried over from the previous round — ObjectChartSchema still leaves compareTo / drillDown / title undeclared, and ObjectChartBlock is still (props: any). 承接者: objectui#8885, open and dispatched",
        "dedup channel declared: REST issue SEARCH is refused by the agent proxy (403, 'sessions are bound to their configured repositories'), and the one targeted MCP search_issues returned empty but its control query could not run (rate limit), so that empty is NOT a reading. The dedup that DID hold is repository- and label-scoped: the four open issues carrying 'package: plugin-charts', with objectui#7946 itself as the control that must appear, and it did. Its limit is that an UNLABELLED duplicate would not appear in it"
      ]
    }

    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

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions