You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
finding(plugin-charts): normalizeSeries reads chartType FIRST on the authored path while the validator now refuses it by name — the two faces disagree #7744
Carved out of the contract review of PR #7737 (card objectui#7694) by the domain:ui PM seat. Filed so the finding survives that PR's Fixes #7694 close — this seat has a measured leak where review findings die as comments on a closed card.
The split, as the reviewer measured it
PR #7737 made chartType a named refusal on the authoring face (ChartDataSeriesSchema), deliberately without touching the reader — that fence was the card's and it was correct for that PR. The reader is byte-identical across the whole PR: git diff a00db9ef9..de8fda691 -- packages/plugin-charts is 0 lines, normalizeChartSchema.ts blob 05a66dfb… at both ends.
ChartRenderer.tsx:65 declares chartType? on the internal dataKey shape. That is the legitimate carrier and ⛔ it is not the defect.
⭐ But the same function also normalizes authored spec-shape series. So an author who skips validation still gets chartType honoured at runtime, while the validator refuses it by name.
That is a pre-existing AGENTS.md #0.1 fallback: a tolerant renderer-side read fossilising a second de-facto contract. It was fenced out of #7737, not resolved.
The disposition, from the review
The dev raised this as its open question 1 with three options — A (leave the reader as is), B (docblock the limb as internal-only / file a card), C (retire the limb at the reader). The reviewer's disposition was B, and its reasoning is worth keeping: A leaves the two faces disagreeing with nothing written down, and C changes what internal producers render, which is a reader decision outside that card.
Suggested direction (⛔ not a ruling)
Make normalizeSeries read chartTypeonly on the internal-shape path, so the authored path and the validator agree — or, if the two paths cannot be told apart at that point, docblock the limb as internal-only and pin the split so it cannot drift silently.
⛔ Do not simply delete the limb: it is lit. The ablation was run twice, most recently by the reviewer on 304 files / 5817 tests: deleting str(raw.chartType) ?? leaves 5817 passed, but deleting the sibling ?? str(raw.type) goes 2 failed (normalizeChartSchema.test.ts "carries stack / yAxis / color on a series"; ChartRenderer.specSeries.test.tsx "honours a spec series[].type override end to end"). The first limb being green on ablation is what makes it removable-looking and exactly why the decision needs to be made deliberately rather than by deletion.
Refs
objectui#7694 (the authoring-face refusal, PR #7737) · objectui#7546 (the parent measurement) · sibling cards from the same census: #7681, #7682, #7690, #7698 · AGENTS.md #0.1.
2 failed(normalizeChartSchema.test.ts 「carries stack / yAxis / color on a series」;ChartRenderer.specSeries.test.tsx 「honours a spec series[].type override end to end」)
{
"issue": 7744,
"status": "done",
"branch": "claude/issue-7744-charttype-internal-only",
"pr": "https://github.com/objectstack-ai/objectui/pull/7815",
"premise_still_valid": true,
"summary": "Took B per the PM ruling: annotated the limb as internal-only and pinned the split; no behaviour change. Premise re-confirmed rather than assumed: the pre-edit blob of normalizeChartSchema.ts is 05a66dfb5b9c9a322761e1e1a694ac7a57af1e54, the exact blob the card names at both ends of PR #7737. The normalizeSeries docblock now names which face owns which spelling and DELEGATES the refusal's rule to ChartDataSeriesSchema in packages/types/src/zod/data-display.zod.ts (objectui#7694 / PR #7737) plus its existing pin chart-series-chart-type-alias-refusal-7694.test.ts, rather than restating it; a two-line comment sits beside the limb. The new case packages/plugin-charts/src/__tests__/chartType-internal-only-7744.test.ts pins the RELATIONSHIP that neither existing file held: the key the validator refuses is the key the reader prefers. It reads ONE fixture writing both spellings with DIFFERENT values ({ name: 'margin', type: 'area', chartType: 'line' }), which is what makes a REORDER observable and not merely a deletion, and it carries a compile-time half over ChartRenderer's series? union arms (chartType is declared on the dataKey arm and on no other). Changeset is the empty-frontmatter form. ChartRenderer.tsx and packages/types/ are untouched; Clause-2 stays no.",
"tests": "ALL COMMANDS FOREGROUND, exit codes captured after redirect (never through a pipe). [1] UNION, run AFTER the final commit, at head 1120ee392, uncached: `pnpm turbo run test type-check --filter=@object-ui/plugin-charts --concurrency=2 --force` exit=0 -- 'Test Files 46 passed (46)', 'Tests 416 passed (416)', 'Tasks: 11 successful, 11 total'. [2] BASELINE, same command before any edit: exit=0, 'Test Files 45 passed (45)', 'Tests 410 passed (410)'. Delta is exactly this PR's one new file and its six cases; no pre-existing test changed verdict. [3] SAME-INPUT/SAME-OUTPUT PROOF (no behaviour change): a probe imports the module and runs 336 input cases through normalizeChartSchema (the only public entry into normalizeSeries) plus comboBaseFamily/effectiveChartFamily/formatterFor/ticksFor/domainFor and the exported sets; 288 of the cases are the chartType/type limb exhaustively (present/absent x 12 values per spelling, incl. disagreeing pairs, empty strings, wrong-typed values, and the same matrix again via `categories`). before-blob 05a66dfb -> sha256 1681dfd534b7f01f798bc9fef45368abba0051bc136c67e3b7cd0c410c7fd896; after-blob 0a2b702c -> sha256 1681dfd534b7f01f798bc9fef45368abba0051bc136c67e3b7cd0c410c7fd896; byte-identical. The probe was itself shown SENSITIVE (otherwise 'identical' is vacuous): reordering the limb in a SCRATCH COPY (repo never touched) moves 49 of the 336 cases. [4] LIT CONTROLS, both run AFTER committing the implementation so HEAD is the correct restore reference. Each: mutation confirmed ON DISK by anchored text counts (old text 0 hits, new text 1 hit) AND by a changed blob hash read before anything else -- an empty hash would read as FAILURE; both ran under `trap restore EXIT INT TERM` with ABSOLUTE paths; restoration proved by blob-hash equality PLUS an empty `git diff HEAD`, i.e. by state, not by whether the trap fired. (a) READER reordered to str(raw.type) ?? str(raw.chartType): vitest exit=1, 'Tests 2 failed | 4 passed (6)'; the two reds are 'the READER honours chartType, and takes it OVER the declared type' and 'THE SPLIT'; message verbatim: \"objectui#7744: `normalizeSeries` no longer resolves the per-series family from `chartType` first. `\\\"area\\\"` here means the limb was REORDERED; `undefined` means it was DELETED...\" expected 'area' to be 'line'. HEAD 0a2b702c / mutated 1f53c339 / post 0a2b702c, git diff HEAD empty. (b) AUTHORING FACE made to accept (ChartDataSeriesSchema.chartType replaced by an accepting enum, the refusal parked on an unused key): vitest exit=1, 'Tests 2 failed | 4 passed (6)'; the two reds are 'the AUTHORING face refuses chartType...' and 'THE SPLIT'. HEAD 1cbacf06 / mutated ae8318b0 / post 1cbacf06, git diff HEAD empty. NO REBUILD WAS NEEDED for either leg and this is measured, not assumed: the root vitest.config.mts aliases '@object-ui/types/zod' to packages/types/src/zod/index.zod.ts (SOURCE, line 411), and the authoring-face mutation DID go red, which is the empirical proof the test read the mutated source rather than a stale dist. So the relationship case is red in BOTH directions while the side-specific cases name WHICH face moved. After both controls: `git status --short` empty, `git diff HEAD` empty, and the pin re-runs '6 passed', exit=0. [5] COMPILE-TIME HALF IS ACTUALLY COMPILED (not a phantom): `tsc -p tsconfig.test.json --noEmit --listFiles | grep -c chartType-internal-only-7744` = 1 (0 would read as NOT MEASURED). [6] DERIVED GATE FAMILY for the changed paths (.changeset/*.md, packages/plugin-charts/src/**), each exit=0, quoting each gate's own verdict line: check-control-bytes 'OK (scanned 6359 tracked text file(s))'; check-changeset-no-major 'No changeset declares a major bump.'; check-changeset-overwrite 'No pre-existing changeset was modified or deleted.'; check-changeset-fixed 'All workspace packages are in the changeset fixed group.'; check-lint-coverage 'lint coverage: 46/46 packages linted, 0 with outstanding errors'; check-type-check-coverage 'type-check coverage: 45/46 via type-check, 0 errors outstanding'; check-unreferenced-sources exit=0. Plus a self-scan for control bytes beyond the gate: `grep -naP '[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]'` over both touched files, no hits. NOTE: objectui has no scripts/pm/dispatch-gates.mjs (that script lives only in objectstack and answers only about its own tree), so this family was derived by hand from .github/workflows/*.yml and package.json. [7] LINT, with the narrowing DECLARED: `pnpm --filter @object-ui/plugin-charts lint` exit=0, 0 errors / 334 pre-existing warnings; the new test file and the edited file contribute 0 errors and 0 new warnings. Repo-wide `pnpm lint` was NOT run locally. Three pieces of evidence that this narrowing excludes nothing: (i) the population is read from eslint's own config, not guessed; (ii) the count is read from `--format json` -- 57 files resolved in this package; (iii) this repo configures NO type-aware linting (no projectService / parserOptions.project in eslint.config.*), so a comment-only edit cannot move any untouched file's verdict. CI runs the full farm regardless. [8] CHANGESET GATE, verdict verbatim, exit=0: 'Compared the working tree with a617bb8d3 (merge-base with origin/main): 3 file(s) changed, 2 of them published source of a package the release covers, 0 of them a manifest whose published contract moved, 0 under a package changesets ignores, 1 changeset(s) added.' / '2 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s): .changeset/7744-chart-type-internal-only.md.' / 'Every one of them has an EMPTY frontmatter -- declared as releasing nothing, which is the explicit exemption and a complete answer to this gate.' (The first run, before the changeset existed, was exit=1 and named @object-ui/plugin-charts; the empty-frontmatter form is the gate's own named pass, not a workaround. Never major. No skip-changeset label applied -- in objectui that label reads nothing and exempts nothing.) [9] NOT MEASURED / NOT RUN, stated as such: I did not re-run the reviewer's repo-wide 304-file/5817-test ablation (the brief said not to re-measure it); I did not run repo-wide `pnpm lint`, `pnpm test` or `pnpm type-check`; I did not wait for CI. PR changed_files comes from GitHub's own API field (3), not from a two-dot diff.",
"mcp_calls": "0 -- every GitHub read and write went through repo-scoped REST (probed with GET first, http=200); no MCP GitHub tool was invoked",
"open_questions": [],
"out_of_scope_findings": []
}
Activity
claude commentedon Sep 5, 2026
Claim: PM loop, round 6 — all-opus lane (Clause-②⚠️ Void condition: 任何删除或改写
no)Session:
session_01KbJQ1y1J12nZxYzFWhP8Q3Branch:
claude/issue-7744-charttype-internal-onlyWorktree:
objectui-issue-7744Domain:
domain:uiFile surface:
packages/plugin-charts/src/normalizeChartSchema.ts(仅文档注释 / 内部形状标注)·packages/plugin-charts/src/__tests__/下钉住这道分裂的新用例 · 本卡自己的.changeset/*.md. ⛔ Stop on breach: 任何让normalizeSeries运行期读出不同值的改动、任何对ChartRenderer.tsx的改动、任何packages/types/的改动。Explain in the report.Container & model: S,
mode:subagent,model: opusClause-②: no —— 按下面的裁定走 B(文档 + pin),不改运行期行为。
str(raw.chartType) ??这条肢、或以任何方式改变normalizeSeries对同一输入的输出 —— 那是 C,会变成 Clause-②yes,停下上报。Serial constraints cleared: 无在飞 PR 触碰
packages/plugin-charts/。PR #7737 已合并(本卡从它的契约复审析出)。在飞的 #7802(plugin-view)· #7798(react/core 文档块)· #7795(scripts)都不相交。PM 裁定:走 B,⛔ 不走 A、⛔ 不走 C
复审席当时的处置就是 B,本席采纳并把它变成命令:
yes。事实(复审席实测,⛔ 不必重测)
normalizeSeries读str(raw.chartType) ?? str(raw.type)——chartType在先。ChartRenderer.tsx:65在内部dataKey形状上声明chartType?—— 那是合法载体,⛔ 不是缺陷。chartType生效,而校验器按名字拒绝它。chartTypeon a chart series by name, pointing attype#7737 全程没有碰过 reader:git diff a00db9ef9..de8fda691 -- packages/plugin-charts0 行,normalizeChartSchema.tsblob05a66dfb…两端一致。⇒ 这是一处先在的
AGENTS.md#0.1 形状:宽容的 renderer 侧读取把第二套事实契约化石化。#7737 把它围栏出去了,没有解决。⛔ 最要紧的一条:不要删这条肢,它是活的
消融跑过两次,最近一次由复审席在 304 files / 5817 tests 上跑:
str(raw.chartType) ???? str(raw.type)normalizeChartSchema.test.ts「carries stack / yAxis / color on a series」;ChartRenderer.specSeries.test.tsx「honours a spec series[].type override end to end」)⭐ 第一条肢在消融下全绿,正是它"看起来可删"的原因,也正是必须慎重而不是一删了之的原因。 全绿只说明没有测试钉住它,不说明没有生产者依赖它。
交付物
normalizeChartSchema.ts把这条肢标注清楚:chartType是内部dataKey形状的载体,不是作者面的键;作者面由ChartDataSeriesSchema按名字拒绝它(PR feat(types): refusechartTypeon a chart series by name, pointing attype#7737)。⭐ 措辞里点名那个拒绝,让读者能一跳到位,⛔ 不要复述它的规则(委托,不要摘要 —— 见 objectui#7765 的落地记录)。验收
normalizeSeries的输出逐字段相同;或更强的:packages/plugin-charts/全套在改动前后用例数与通过数完全一致。trap … EXIT INT TERM下用git checkout HEAD -- <绝对路径>还原,以 blob hash 相等 + 空git diff HEAD证明 —— ⛔ 证明靠状态,不靠 trap 是否触发;变异前先用 hash 确认落盘(空 hash 读作失败)。pnpm --filter @object-ui/plugin-charts test与type-check(走 turbo),报出文件数/用例数与 exit code。变更集
改动落在发版包
src/下 ⇒ 大概率需要。⛔ 不要凭规则推断:跑node scripts/check-changeset-presence.mjs,判词原样抄进报告;欠就写空 frontmatter。⛔ 永不写major。① 裸
tsc --noEmit不是本仓的门(turbotype-checkdependsOn ^build,到不了非包目录;构建前的TS2882是未满足的前提、不是结果)② 本容器没有「未认证的探测」,匿名样子的POST是真写入,探通道只用GET③scripts/__tests__/check-doc-links.test.ts共享盒上 15s 超时是已知 flake ④ ⛔new URL(rel, import.meta.url)被 Vite 重写 ⑤ ⛔ 不用两点式origin/main..HEAD报文件数(并发落地会虚报,实测 4 vs 真实 2)。契约复审
Clause-② 是⚠️ 走成 C 就是
no(走 B)⇒ 不排在契约复审后面。yes—— 停下上报,不要自行改档。Generated by Claude Code
claude commentedon Sep 5, 2026
os-dev-report
{ "issue": 7744, "status": "done", "branch": "claude/issue-7744-charttype-internal-only", "pr": "https://github.com/objectstack-ai/objectui/pull/7815", "premise_still_valid": true, "summary": "Took B per the PM ruling: annotated the limb as internal-only and pinned the split; no behaviour change. Premise re-confirmed rather than assumed: the pre-edit blob of normalizeChartSchema.ts is 05a66dfb5b9c9a322761e1e1a694ac7a57af1e54, the exact blob the card names at both ends of PR #7737. The normalizeSeries docblock now names which face owns which spelling and DELEGATES the refusal's rule to ChartDataSeriesSchema in packages/types/src/zod/data-display.zod.ts (objectui#7694 / PR #7737) plus its existing pin chart-series-chart-type-alias-refusal-7694.test.ts, rather than restating it; a two-line comment sits beside the limb. The new case packages/plugin-charts/src/__tests__/chartType-internal-only-7744.test.ts pins the RELATIONSHIP that neither existing file held: the key the validator refuses is the key the reader prefers. It reads ONE fixture writing both spellings with DIFFERENT values ({ name: 'margin', type: 'area', chartType: 'line' }), which is what makes a REORDER observable and not merely a deletion, and it carries a compile-time half over ChartRenderer's series? union arms (chartType is declared on the dataKey arm and on no other). Changeset is the empty-frontmatter form. ChartRenderer.tsx and packages/types/ are untouched; Clause-2 stays no.", "tests": "ALL COMMANDS FOREGROUND, exit codes captured after redirect (never through a pipe). [1] UNION, run AFTER the final commit, at head 1120ee392, uncached: `pnpm turbo run test type-check --filter=@object-ui/plugin-charts --concurrency=2 --force` exit=0 -- 'Test Files 46 passed (46)', 'Tests 416 passed (416)', 'Tasks: 11 successful, 11 total'. [2] BASELINE, same command before any edit: exit=0, 'Test Files 45 passed (45)', 'Tests 410 passed (410)'. Delta is exactly this PR's one new file and its six cases; no pre-existing test changed verdict. [3] SAME-INPUT/SAME-OUTPUT PROOF (no behaviour change): a probe imports the module and runs 336 input cases through normalizeChartSchema (the only public entry into normalizeSeries) plus comboBaseFamily/effectiveChartFamily/formatterFor/ticksFor/domainFor and the exported sets; 288 of the cases are the chartType/type limb exhaustively (present/absent x 12 values per spelling, incl. disagreeing pairs, empty strings, wrong-typed values, and the same matrix again via `categories`). before-blob 05a66dfb -> sha256 1681dfd534b7f01f798bc9fef45368abba0051bc136c67e3b7cd0c410c7fd896; after-blob 0a2b702c -> sha256 1681dfd534b7f01f798bc9fef45368abba0051bc136c67e3b7cd0c410c7fd896; byte-identical. The probe was itself shown SENSITIVE (otherwise 'identical' is vacuous): reordering the limb in a SCRATCH COPY (repo never touched) moves 49 of the 336 cases. [4] LIT CONTROLS, both run AFTER committing the implementation so HEAD is the correct restore reference. Each: mutation confirmed ON DISK by anchored text counts (old text 0 hits, new text 1 hit) AND by a changed blob hash read before anything else -- an empty hash would read as FAILURE; both ran under `trap restore EXIT INT TERM` with ABSOLUTE paths; restoration proved by blob-hash equality PLUS an empty `git diff HEAD`, i.e. by state, not by whether the trap fired. (a) READER reordered to str(raw.type) ?? str(raw.chartType): vitest exit=1, 'Tests 2 failed | 4 passed (6)'; the two reds are 'the READER honours chartType, and takes it OVER the declared type' and 'THE SPLIT'; message verbatim: \"objectui#7744: `normalizeSeries` no longer resolves the per-series family from `chartType` first. `\\\"area\\\"` here means the limb was REORDERED; `undefined` means it was DELETED...\" expected 'area' to be 'line'. HEAD 0a2b702c / mutated 1f53c339 / post 0a2b702c, git diff HEAD empty. (b) AUTHORING FACE made to accept (ChartDataSeriesSchema.chartType replaced by an accepting enum, the refusal parked on an unused key): vitest exit=1, 'Tests 2 failed | 4 passed (6)'; the two reds are 'the AUTHORING face refuses chartType...' and 'THE SPLIT'. HEAD 1cbacf06 / mutated ae8318b0 / post 1cbacf06, git diff HEAD empty. NO REBUILD WAS NEEDED for either leg and this is measured, not assumed: the root vitest.config.mts aliases '@object-ui/types/zod' to packages/types/src/zod/index.zod.ts (SOURCE, line 411), and the authoring-face mutation DID go red, which is the empirical proof the test read the mutated source rather than a stale dist. So the relationship case is red in BOTH directions while the side-specific cases name WHICH face moved. After both controls: `git status --short` empty, `git diff HEAD` empty, and the pin re-runs '6 passed', exit=0. [5] COMPILE-TIME HALF IS ACTUALLY COMPILED (not a phantom): `tsc -p tsconfig.test.json --noEmit --listFiles | grep -c chartType-internal-only-7744` = 1 (0 would read as NOT MEASURED). [6] DERIVED GATE FAMILY for the changed paths (.changeset/*.md, packages/plugin-charts/src/**), each exit=0, quoting each gate's own verdict line: check-control-bytes 'OK (scanned 6359 tracked text file(s))'; check-changeset-no-major 'No changeset declares a major bump.'; check-changeset-overwrite 'No pre-existing changeset was modified or deleted.'; check-changeset-fixed 'All workspace packages are in the changeset fixed group.'; check-lint-coverage 'lint coverage: 46/46 packages linted, 0 with outstanding errors'; check-type-check-coverage 'type-check coverage: 45/46 via type-check, 0 errors outstanding'; check-unreferenced-sources exit=0. Plus a self-scan for control bytes beyond the gate: `grep -naP '[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]'` over both touched files, no hits. NOTE: objectui has no scripts/pm/dispatch-gates.mjs (that script lives only in objectstack and answers only about its own tree), so this family was derived by hand from .github/workflows/*.yml and package.json. [7] LINT, with the narrowing DECLARED: `pnpm --filter @object-ui/plugin-charts lint` exit=0, 0 errors / 334 pre-existing warnings; the new test file and the edited file contribute 0 errors and 0 new warnings. Repo-wide `pnpm lint` was NOT run locally. Three pieces of evidence that this narrowing excludes nothing: (i) the population is read from eslint's own config, not guessed; (ii) the count is read from `--format json` -- 57 files resolved in this package; (iii) this repo configures NO type-aware linting (no projectService / parserOptions.project in eslint.config.*), so a comment-only edit cannot move any untouched file's verdict. CI runs the full farm regardless. [8] CHANGESET GATE, verdict verbatim, exit=0: 'Compared the working tree with a617bb8d3 (merge-base with origin/main): 3 file(s) changed, 2 of them published source of a package the release covers, 0 of them a manifest whose published contract moved, 0 under a package changesets ignores, 1 changeset(s) added.' / '2 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s): .changeset/7744-chart-type-internal-only.md.' / 'Every one of them has an EMPTY frontmatter -- declared as releasing nothing, which is the explicit exemption and a complete answer to this gate.' (The first run, before the changeset existed, was exit=1 and named @object-ui/plugin-charts; the empty-frontmatter form is the gate's own named pass, not a workaround. Never major. No skip-changeset label applied -- in objectui that label reads nothing and exempts nothing.) [9] NOT MEASURED / NOT RUN, stated as such: I did not re-run the reviewer's repo-wide 304-file/5817-test ablation (the brief said not to re-measure it); I did not run repo-wide `pnpm lint`, `pnpm test` or `pnpm type-check`; I did not wait for CI. PR changed_files comes from GitHub's own API field (3), not from a two-dot diff.", "mcp_calls": "0 -- every GitHub read and write went through repo-scoped REST (probed with GET first, http=200); no MCP GitHub tool was invoked", "open_questions": [], "out_of_scope_findings": [] }Generated by Claude Code
claude commentedon Sep 5, 2026
落地记录 —— PR #7815 已合入(本 lane 今日第 26 次落地)
merged: true@ 2026-09-05T23:46:18Z,squash3faaa7d0f#7744closed_at23:46:20Zremoved_from_merge_queue—— ⇒ 那不是被踢出后重排,是它自己那一轮 merge-group CI 跑得久。⛔ 这段等待期间不要因为"别人都合了就它没合"而去动它。⭐ 三条值得留下来的读数
1. 裁定选的是 B(标注内部专用 + 钉住 split),交付物里"没有行为变化"是被证明的,不是声称的。
一个探针跑 336 个输入用例过
normalizeChartSchema(normalizeSeries唯一的公开入口),其中 288 个是穷举那条chartType/type肢(每种拼法 12 个值 × 有/无 × 互相矛盾的组合 × 空串 × 错类型,再经categories走一遍):逐字节相同。
⭐ 而这条"相同"之所以不是空话,是因为探针本身被证明是敏感的:在一个scratch 副本上(仓库一行没动)把那条肢重排,336 个里有 49 个移动。⇒ 「没变」是一个有分辨力的读数,不是一个测不出差别的仪器给出的沉默。
2. ⭐⭐ 新钉子钉的是关系**,不是任何一边 —— 而且它用一个"两边写不同值"的 fixture 让重排可观测。**
两条拼法写不同的值 ⇒ 读点偏好谁,一读便知。⭐ 这使得「校验器拒绝的那个键,正是读点优先采用的那个键」这条关系第一次被钉住 —— 此前
chart-series-chart-type-alias-refusal-7694.test.ts只钉了拒绝那一半,normalizeSeries那一半没人钉,两边各自都能改而不被发现。3. 双向消融把这条关系锁死在两个方向上。
str(raw.type) ?? str(raw.chartType)chartType」与「THE SPLIT」ChartDataSeriesSchema.chartType换成接受型枚举)chartType」与「THE SPLIT」⭐ 关系用例(THE SPLIT)在两个方向都红,而各自的单边用例指出是哪一边动了。⇒ 任何一边单独漂移都会被抓住,并且报告会说清楚是哪一边。
vitest.config.mts把@object-ui/types/zod别名到packages/types/src/zod/index.zod.ts(源码,第 411 行),而那次变异确实变红了 —— 这是"测试读的是被变异的源码而不是陈旧的 dist"的经验证据,⛔ 不是靠推理得出的。Generated by Claude Code