feat(lint): react 页发布闸门改为 PARSE ChartAggregateSchema,不再手写重推它的规则 (#5020) - #5608
Merged
Conversation
…5020) `<ObjectChart aggregate={{…}}>` was judged by a gate that RE-DERIVED its schema: a local `CHART_FUNCTIONS` copy of the function vocabulary and a hand-written twin of the count/field refinement. Two implementations of one contract, free to drift — and since unknown-key handling is a property of a PARSE rather than of a list of `if`s, a gate with no unknown-key check at all. `validate-react-page-props` now calls `ChartAggregateSchema.safeParse()` on a statically resolvable literal, exactly as #5022 did for `ChartDrillDownSchema` one prop over, and both hand-derived copies are deleted. Newly reported, all previously silent: a missing `function`, a non-string `field`, a present-but- wrong `groupBy`, a non-object `aggregate`. Two messages are re-worded because they now come from the schema (FROM/TO in the changeset). zod 4 collapses a union's arm failures into one `invalid_union` whose message is the bare "Invalid input", so the arms are unpacked into the finding — otherwise `groupBy` rejections would say nothing an author can act on, and the same collapse will hide #5583's strict `unrecognized_keys`. An absent `groupBy` reports at `warning`, alone among the graded violations: the schema and the published react-blocks type declare it required while objectui's `ObjectChart` honours its absence (`|| schema.xAxisKey`) and `chartAggregateCategoryKey` documents the ungrouped single-row result. Gating it would enforce a declaration the platform does not itself keep; #5583 decides which side moves. The unknown-key hole is NOT closed by this commit and is pinned open on purpose: both schemas are still STRIP, so the parse drops `groupby` / `dateGranularty` instead of reporting them. Wiring the parse is the precondition, not the closing (#5583). Records updated in the three places the strictness ledger requires: `chart.zod.ts` header prose (schema body untouched), `chart.test.ts` pin comments (assertions unchanged — the posture did not move), and the ledger's two `chart.zod.ts` rows `no gate` → `authorable` with the counts artifact regenerated (`no gate` 31 → 29, `authorable` 11 → 13). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GX3sL71LFq8m2usg6VqTSE
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 2 package(s): 109 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
os-zhuang
marked this pull request as ready for review
August 5, 2026 20:24
Contributor
Author
|
CI 归因(一次性):ESLint job 红 = main 自带的 Generated by Claude Code |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #5020
按 issue 上 2026-08-05 的 PM 裁定(R1/R2/R3)执行的两步走第一步:只做
packages/lint侧接 parse,spec schema 本体零改动(收紧归 #5583)。前提复核(对 origin/main,开工前)
validate-react-page-props.ts:301手写CHART_FUNCTIONS、:356/:363 手推 refinementChartDrillDownSchema.safeParse先例仍在safeParse({function:'count',groupBy:'status',groupby:'status'})success 且丢groupby;嵌套dateGranularty同groupBy的失败以一条invalid_union、message 为字面量Invalid input到达,具名消息只在issue.errors里groupBy缺席ObjectChart.tsx:640:schema.aggregate?.groupBy || schema.xAxisKey;spec 自己的chartAggregateCategoryKeyTSDoc 也写明「未分组聚合返回单行、没有分类列」groupBy被声明为必填react-blocks.ts:245的已发布类型串都必填 —— 三方不一致确实存在,这正是 R2 降 warning 的依据examples/app-showcase/src/ui/pages/renewals-pipeline.page.ts:138完全合法;技能语料 / 文档片段里的 aggregate 也都带合法groupBy,无一处被新闸门打断裁定与实码无冲突,范围未收缩。
R1 —— 接 parse,词表与 refinement 收敛回 schema
checkChartAggregate()照checkChartDrillDown的形状调ChartAggregateSchema.safeParse();CHART_FUNCTIONS与手推的 count/field refinement 删除。NOT_STATIC短路语义不变(ADR-0072 D1)。解包
invalid_union:zod 4 把 union 的分支失败塌成一条顶层 issue、message 只剩Invalid input,直接透传等于没说话。现在把各 arm 的具名消息拼进同一条 finding:这条解包在 #5583 之后更要紧:strict 分支的
unrecognized_keys会以完全相同的方式塌缩,具名 surface + 改名建议要靠它到达作者。另外补了一件 zod 不做的事:回显作者写下的值。
Invalid option: expected one of …不带 received 值,而被删掉的手写检查是带的;现在按 issue path 从输入里取回(通用逻辑,不在消费端复述任何契约知识)。R2 —— 严格度分级
functionaggregate.function: Invalid option: expected one of …(nothing is set there)function不在枚举内(received "median")field非字符串aggregate.field: Invalid input: expected string, received numberfieldaggregate不是对象aggregate must be a configuration object, not string.groupBy存在但类型错groupBy缺席groupBy缺席是唯一降级项:它是唯一有活渲染路径兑现的形状,按 #5068 条款「渲染器兑现的键先声明后设闸」不设 error。finding 自带解释并点名 #5583(产品问题「无分组单值图是否受支持」在那边裁)。分级是按 issue 分的,不是按整个 aggregate:{ function: 'median' }同时产出一条 error(function)+ 一条 warning(groupBy)。两个 schema 仍是 STRIP,所以这条 parse 依旧把未知键静默丢掉 ——
groupby/dateGranularty今天照旧让图退化成单点而 build 全绿。接 parse 是收紧的前置条件,不是收紧本身(.strict()是 parse 的属性,在此之前根本没有 parse 可收紧)。validate-react-page-props.test.ts因此显式钉住今日容忍度并点名 #5583。两条 pin 都做了判别性加固,不靠「什么都没产出」蒙绿(#5046 的坑):同一个 fixture 里在未知键旁边种一个只有 parse 才报得出的违规,断言「恰好 1 条 finding、来自 schema 的措辞、且不含Unrecognized key」。反向验证证明这半边是判别性的 —— 把旧规则放回去,pin 立刻红,报的正是手写措辞aggregate.function "kount" is not an aggregation this chart can run。R3 —— 三处记录随本 PR 翻
packages/spec/src/ui/chart.zod.ts—— 仅注释散文(文件头 + schema 旁那段大块注释),schema 本体零改动;批 15 那句「DO NOT convert before that is decided」标注为已满足并指向 spec:ChartAggregateSchema与ChartGroupBySchemaobject 分支转 strictObject —— #5020 parse 接通后的收紧半边(#4001 后续批) #5583,而不是被推翻。packages/spec/src/ui/chart.test.ts—— 仅 pin 注释改指 spec:ChartAggregateSchema与ChartGroupBySchemaobject 分支转 strictObject —— #5020 parse 接通后的收紧半边(#4001 后续批) #5583;两条「still STRIPS — deliberate」断言本体不动,姿态未变必须保持绿(75 tests 全绿),常驻 BFS 断言不动。docs/audits/2026-07-unknown-key-strictness-ledger.md—— :636 triage 行与 :849 strip 行no gate→authorable,并在汇总段落把no gate桶里的 chart 一对划掉。Class单元格是机读的,只能放一个 verdict:~~no gate~~ **authorable**会被check:strictness-ledger判为「not a verdict」(我先踩了一次),所以历史写在证据列。.counts.md按 os-regen 规程用gen:strictness-ledger整体重生成,未手改数字:no gate31 → 29、authorable11 → 13,恰好这 2 个站点。触碰面声明(spec 车道地带)
chart.zod.ts/chart.test.ts/ 台账三个文件均属 spec 车道散文面。push 前git log --oneline origin/main -- 这些文件复核:最近一次改动是 e900015(#5435 的 drillDown target,已在我的 base 里),当日无其它人在改;git ls-remote --heads origin也无 chart/ledger/5583 相关在飞分支。验证
pnpm --filter @objectstack/lint test—— 59 files / 1335 passed | 4 skipped;单文件validate-react-page-props.test.ts135 passed(新增约 49 条,含 37 行表驱动词表逐绿:5 个函数 × 裸串 arm + 结构 arm + 5 个 granularity,外加 alias 与 count 带 field)pnpm --filter @objectstack/lint typecheck—— 绿(过程中吃了一记 TS7006:Array.isArray()会把ReadonlyArray窄化成any[],已改成可选属性判空,注释里记下了)pnpm --filter @objectstack/spec的src/ui/chart.test.ts—— 75 passedpnpm --filter @objectstack/spec check:generated—— 10/10 绿(含check:strictness-ledger、check:docs、check:authorable-surface)node scripts/check-nul-bytes.mjs绿;另对改动文件跑了越过闸门盲区的自查grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f]',干净flags a non-count function with nothing to aggregate,因为它现在断言 schema 原话,属预期的 FROM/TO 后果)。两条 STRIP 容忍断言的容忍那半在新旧代码下都绿(两版都不查未知键),这一点如实说明,不假装是本 PR 的成果;判别性由上面那半提供。给 review 的一条注意
在飞的旧 changeset
.changeset/objectchart-aggregate-result-columns.md把react-chart-aggregate-invalid描述成「(error)」,而本 PR 之后它多了一个 warning 分支。没有去改别人那份 pending changeset(冲突磁铁、且不在本单范围);本 PR 自己的 changeset 已把分级写清楚。Generated by Claude Code