从 #4001 批 15 的门测量中分离出来,**未指派**。批 15 已按测量把这两个站点留开并三处留痕,本 issue 是它指向的那件事。 ## 测量结果(2026-08-03/04,均带对照) `packages/spec/src/ui/chart.zod.ts` 的 7 个 object 站点里,5 个从 `dashboard` / `report` 两个 metadata-type root 可达(`DashboardWidget.chartConfig`、`ReportChartSchema`),已在批 15 收紧。**剩下 2 个 —— `ChartAggregateSchema` 和 `ChartGroupBySchema` 的 object 分支 —— 情况不同,而且不是「死槽位」**: | 测量 | 结果 | |---|---| | 承载键 | ✅ **活的**。`aggregate` 是 react 层 `< ObjectChart objectName aggregate={…} >` 的真实可授权 prop(ADR-0081),发布在生成的 react-blocks 契约里,objectui 的 `ObjectChart` 读 `schema.aggregate` 去跑查询 | | 从 24 个 metadata-type root + `ObjectStackSchema` 做 BFS | ❌ `UNREACHABLE`(同一次运行里,上述 5 个是 `root-graph`,批 13 的 no-door 形状是 `UNREACHABLE` —— 双向对照都成立) | | 三个仓库里的 `.parse()` / `.safeParse()` | ❌ 除本文件单测外没有 | ## 问题 唯一实际审查作者所写 `aggregate` 的东西是 react 页发布 lint(`packages/lint/src/validate-react-page-props.ts`),它**手写重推了这个 schema 的规则**:`CHART_FUNCTIONS` 复制了函数词表,`fn !== 'count' && !field` 复制了那条 refinement,结果列命名另算一份。**它从不检查未知键。** `react-blocks.ts` 里这个 prop 是一条**手写的类型字符串**;旁边的 Zod schema 不是契约的生成来源。 所以今天: ```ts < ObjectChart objectName="invoice" aggregate={{ function: 'sum', field: 'total', groupby: 'status' }} / > // ^^^^^^^ 静默丢弃 ``` `groupby`(而非 `groupBy`)被剥掉 → 聚合退化成无分组、单行返回 → 图表画出坐标轴和一个点,`build`/`validate` 全绿。`dateGranularty` 同理:日期不分桶,趋势线变成一根。这正是 #4001 的原始失效模式,只是发生在 react 层。 ## 为什么批 15 没有顺手收紧 `.strict()` 是 **parse 的属性**,这里没有 parse。收紧只会花掉一次 v17 破坏性变更让文件看起来完成,并留下账本自己警告过的东西 —— *「一个被精确校验的死槽位是更有说服力的谎言」*(#4583)。而且这里比批 13 的情形更需要小心:**这套词汇是活的**,所以正确动作不是 ADR-0049 退役,而是把闸门接上。 批 15 因此在账本的分类规则表里加了第四类 **`no gate`**,与批 13 的 `no door` 并列 —— 两者都「没有 parse」,但处方相反: | | 承载键 | parse | 正确的下一步 | |---|---|---|---| | `no door`(批 13) | 无 | 无 | ADR-0049 enforce-or-remove | | `no gate`(批 15) | **活的** | 无 | **接上 parse**;退役会打断一个能用的功能 | ## 建议(contract-first) 让 `validate-react-page-props.ts` 用 `ChartAggregateSchema.safeParse()` 判定 `aggregate`,而不是重推它的规则: - 未知键立刻有了闸门,且错误信息可以走本战役的 `strictObject` 路子(点名 + 改名建议); - `CHART_FUNCTIONS`、count/field refinement 两份重复实现收敛成一份 —— 目前它们已经能各自漂移; - 之后这两个站点就是普通的 `authorable`,收紧是一行改动。 需要先确认的一点:react 页 prop 是**静态字面量抽取**的结果,`NOT_STATIC` 的值不能判。所以是「能静态判定时才 parse」,与现有 `if (aggregate === undefined || aggregate === NOT_STATIC) return;` 的短路保持一致。 ## 参考 - #4001(战役)、批 15 的 PR(账本 `ui/` 行 + `chart.zod.ts` 头部注释 + `chart.test.ts` 的常驻 BFS 断言 —— 一旦有人给 `aggregate` 加上 metadata 承载键,那条断言会变红并把人指回这里) - #4988(批 13 的 `no door` 裁决)、#4583(精确校验的死槽位) - ADR-0081(react 层)、ADR-0021(dataset 路径的对照)
Activity
os-zhuang commentedon Aug 5, 2026
认领:PM 循环第 5 轮(devx 车道)
会话:
session_01GX3sL71LFq8m2usg6VqTSE分支:
claude/issue-5020-chart-aggregate-gateWorktree:
objectstack-issue-5020域:
domain:devx文件面:
packages/lint/src/validate-react-page-props.ts+ 其测试(既有文件,不新增规则注册)。⛔ 不触packages/spec/src/ui/chart.zod.ts本体(#4001 战役属地,本单只消费其导出)。前提已核:origin/main 该文件 :301 仍手写
CHART_FUNCTIONS、:356 仍手推 refinement;同文件 :288 已有ChartDrillDownSchema.safeParse同款先例 —— 修法有仓内锚,照该形状把aggregate/groupBy接上ChartAggregateSchema/ChartGroupBySchema。NOT_STATIC 短路语义保持。Generated by Claude Code
os-zhuang commentedon Aug 5, 2026
裁定(devx 车道 PM,
session_01GX3sL71LFq8m2usg6VqTSE,2026-08-05):dev 的needs_decision停车完全正确 —— 它实测证伪了正文「未知键立刻有了闸门」的主张(两 schema 均 STRIP 姿态,仓内常驻 pin 自证),并找到了同类兄弟单 #5068 今日的维护者裁决。三问均落在该同日先例的射程内,按先例直接裁定(否决窗口,维护者可推翻):Q1 → A(两步走,#5068 方向 A 照抄):本单只做 packages/lint —— 接
ChartAggregateSchema.safeParse、删CHART_FUNCTIONS与手推 refinement、解包invalid_union保留具名 arm 消息;strictObject 收紧归 #4001 后续批,跟进单本轮已立(见下),过渡期缺口用 pin 测试 + 注释点名跟进单钉住。C(消费端复刻 strictObject)按防 AI 犯错轴否决 —— 契约知识进消费者正是 #4001 要删的形状。Q2 → A 类逐字执行 + groupBy 缺席单独 warning:schema、已发布 react-blocks 类型、渲染器三方一致的类(
function必填/枚举、field字符串、aggregate对象)按 error 执行,declared = enforced;groupBy 缺席这一类降 warning —— 它是唯一有活渲染路径(ObjectChart.tsx:640 的|| xAxisKey兜底)兑现的形状,恰是 #5068 分级条款针对的「设闸打断能用的东西」情形。「无分组单值图是否受支持形状」(决定 schema 声明 optional 还是收紧渲染器)是 spec 面产品问题,写进跟进单交战役/维护者,本 PR 不拍。Q3 → A(三处记录随 parse PR 一起翻):chart.zod.ts 仅头部散文、chart.test.ts 仅 pin 注释(断言本体不动 —— STRIP 姿态未变,pin 应保持绿)、台账 :636/:849 行⚠️
no gate→authorable并指向跟进单。账本自己的「三处一起改或都不改」纪律 + #5068 把行翻转计入 parse PR 完成定义,共同支持 A;schema 本体零改动,#4001 属地未破。.counts.md是生成物:若gen:strictness-ledger有 diff 按 os-regen 规程整体重生成,禁手改。同一认领续派新 dev 执行,派发词含全部三裁。
Generated by Claude Code
os-zhuang commentedon Aug 5, 2026
实施完成(续派 dev,
session_01GX3sL71LFq8m2usg6VqTSE,分支claude/issue-5020-chart-aggregate-gate):draft PR #5608。三裁全部按裁定落地,前提已对 origin/main 复核且无冲突(渲染器兑现
groupBy缺席、react-blocks.ts:245声明必填、两 schema 均 STRIP、zod 4 union 塌缩,四项均实测)。ChartAggregateSchema.safeParse()接上,CHART_FUNCTIONS与手推 count/field refinement 删除;invalid_union解包,各 arm 具名消息进同一条 finding(spec:ChartAggregateSchema与ChartGroupBySchemaobject 分支转 strictObject —— #5020 parse 接通后的收紧半边(#4001 后续批) #5583 的 strictunrecognized_keys会以同样方式塌缩,靠这段解包送达作者)。额外补了 zod 不给的 received 值回显(按 issue path 从输入取回,不在消费端复述契约)。function必填/枚举、field字符串、aggregate对象、sum/avg/min/max 缺 field 一律 error;groupBy缺席单独 warning 不阻断,分级按 issue 而非整个 aggregate({ function: 'median' }同时得一条 error + 一条 warning)。chart.zod.ts仅散文、chart.test.ts仅 pin 注释(两条 STRIP 断言本体不动、保持绿)、台账 :636/:849no gate→authorable;.counts.md走gen:strictness-ledger整体重生成,no gate31 → 29、authorable11 → 13,正好这 2 个站点,未手改数字。Unrecognized key。反向验证方向先判后跑:预判「放回手写限枝 → 新 pin 红」,实跑 11 条红与预判一致;同时如实记录两条 STRIP 容忍断言的容忍那半在新旧代码下都绿(两版都不查未知键),没有把它算作本 PR 的成果。验证:lint 包 1335 passed / typecheck 绿、
chart.test.ts75 passed、speccheck:generated10/10、NUL 与控制字符自查干净、eslint 干净。无 out-of-scope 新发现需另立单。Generated by Claude Code
os-zhuang commentedon Aug 5, 2026
验收(devx 车道 PM,
session_01GX3sL71LFq8m2usg6VqTSE,2026-08-05):ACCEPT → PR #5608,转 ready 并挂 auto-merge。ChartAggregateSchema与ChartGroupBySchemaobject 分支转 strictObject —— #5020 parse 接通后的收紧半边(#4001 后续批) #5583 后同机制更要紧的前瞻)+ 通用 received 回显;R2 按 issue 分级,四类既往静默转 error、groupBy 缺席唯一 warning 点名 spec:ChartAggregateSchema与ChartGroupBySchemaobject 分支转 strictObject —— #5020 parse 接通后的收紧半边(#4001 后续批) #5583;R3 三处翻转干净(schema 本体零动、pin 断言零动、台账机读单元格约束实测发现并正确处理、.counts.md 整体重生成恰好 2 站点)。??别名读法 (#5017) #5046 空绿坑的判别性加固(未知键旁种 parse 才能报的违规)是超出裁定要求的正当防御;词表表驱动用schema.options钉表防静默欠覆盖;消费半径按 authored aggregate 全仓扫过。check:engine-double-contract挂在 #5584 刚落地的action-execution-calldata-not-found.test.ts(2 个 double 未接assertEngineDeleteDispatch,基线无条目)——所有新 PR 的 ESLint job 都过不去 #5604 的 base 红,按 feat(lint): 禁止把引擎查询选项擦成any,存量入计数 baseline 只减不增 (#4918) #5600 上已留的归因口径处理(等修复合 main 重跑),不归本 PR。Generated by Claude Code
feat(lint): the react-page publish gate PARSES ChartAggregateSchema (o…