fix(lint): object/missing-name-field 认 nameField、不再采信已退役的 titleFormat (#6108) - #6338
Merged
Merged
Conversation
…#6108) `object/missing-name-field` 的谓词从不读 `obj.nameField`,却仍采信 `obj.titleFormat`, 于是同一个包里两条规则互相矛盾:`validate-record-title.ts` 把每一处 `titleFormat` 都报成 `title-format-retired` 并按 ADR-0079 指示迁移到 `nameField`,共享判定 `objectTitleCompleteness` 也从不读它——照这条迁移建议做的对象反而多得一条 "records will display as raw IDs" suggestion。下游 hotcrm main 实测 6 命中 4 误报, 四个对象都显式声明了 `nameField`。 - 谓词补读 `nameField`,摘掉 `titleFormat` 一支;`primaryField` 与 name-like 两支不动。 - titleFormat-only 的对象因此新得本规则一条 suggestion:刻意翻转,不是回归—— 这类对象正是 ADR-0079 要迁移的那批,`validate-record-title` 今天已对其同时报 `title-format-retired` 与 `title-unresolvable`,两条规则从此判定一致。 - 提示文案只点名作者真能声明的面,并新增 `fix` 说明 `titleFormat` 不算标题面。 旧文案里的 `primaryField` 不再出现:实测 `ObjectSchema.create()` 以 `unrecognized_keys` 拒收该键(另立 #6326),提示不该广告一个会被 schema 硬拒的键。 - 新增断言复刻 hotcrm 对照面(6 进 2 出),并覆盖 nameField / 无 name 面 / primaryField / name-like / titleFormat-only 五条路径。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BDmDsu2575gDxeMCxXhDE3
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 1 package(s): 3 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
hotlong
marked this pull request as ready for review
August 7, 2026 14:32
This was referenced Aug 7, 2026
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 #6108
object/missing-name-field的谓词从不读obj.nameField,却仍采信 ADR-0079 已退役的obj.titleFormat。净效果:照平台自己的迁移建议把titleFormat换成nameField的对象,反而多得一条 "records will display as raw IDs" suggestion;守着已退役的键不动的对象反而干净。
1. 前提复核(在
origin/main上核对过,前提成立)packages/lint/src/data-model-rules.ts:382-386(合并基a682670)现行形状,与 issue 正文从
dist读到的完全一致:nameField在整个packages/lint/src/data-model-rules.ts中出现 0 次。前提为真。2. titleFormat 处置的一致性论证(动手前先读了
validate-record-title.ts)只读对照,未改动该文件本体。同包与 spec 三处现行处置一致地不把
titleFormat当作标题面:titleFormat的现行处置packages/lint/src/validate-record-title.ts:82-97title-format-retired(warning),文案 "titleFormat is retired (ADR-0079) — migrate to nameField"packages/spec/src/data/display-name.ts:311-318(objectTitleCompleteness)nameField ?? displayNameField ?? 派生,从不读titleFormatpackages/spec/src/data/object.zod.ts:1563describe前缀即[DEPRECATED → nameField (ADR-0079)],"render-only … the server cannot return or query it"packages/spec/src/migrations/registry.ts:59-68object-titleFormat-to-nameField,期望态是「每个带titleFormat的对象都声明nameField」同包另外两条规则也已经只读规范指针:
validate-semantic-roles.ts:190(
[nameField, primaryField, displayNameField])与validate-searchable-fields.ts:199(
nameField ?? displayNameField)——两者都不读titleFormat。data-model-rules.ts是全仓唯一把titleFormat当标题面、且唯一不读nameField的消费者。因此「摘掉 titleFormat ⇒ titleFormat-only 的对象新得一条 missing-name-field」与 ADR-0079
指向一致而非矛盾:这类对象正是迁移目标,
validate-record-title今天已对同一个对象同时报title-format-retired与title-unresolvable(该双报由packages/lint/src/validate-record-title.test.ts:73钉住)。修前是两条规则对同一对象给出相反判断,修后一致。派单里「若现行处置与此推理冲突则停手」的条件未触发。
实测这一翻转的量级:全仓 74 个带
titleFormat的源文件中,有 22 个系统对象在摘除后失去唯一逃逸口(全部是
nameField/displayNameField/name-like 字段皆无的复合模板对象,例如sys-team-member('{user_id} in {team_id}')、sys-record-share、notification-delivery)。这 22 个正是
title-unresolvable今天已经在报的同一批——两条规则从「互相矛盾」收敛到「重复确认」。3. 谓词 before / after
primaryField与 name-like 两支逐字未动。提示文案改为只点名作者真正能声明的面,并新增fix提示(见 §7 第 3 条)。4. hotcrm 对照面 → fixture 映射(以 fixture 复刻,不跨仓)
新增
describe('lintDataModel — object/missing-name-field (ADR-0079 title face)'),六条断言。核心一条把 issue 正文那张表整个喂进
lintDataModel,断言只剩两个 line-item 被报:nameFieldobjects[0]objects[1]objects[2]+ 独立用例 (a)objects[3]objects[4]objects[5]+ 独立用例 (b)控制面断言写成
expect(flagged(objects)).toEqual(['objects[4].fields', 'objects[5].fields'])——用完整路径列表而不是计数,以免「因为什么都没产生所以断言通过」的空绿。
另外三条:
crm_forecast_period只有primaryField,crm_campaign只有 name-like 的name;既有那条primaryField: 'code'用例里code本身就是 name-like,两支互相遮蔽,故另取样本);validate-record-title的双报;nameField,fix必须同时出现ADR-0079与titleFormat。5. 反向验证(先申报,后执行,两段对照)
申报(执行前写定):只把谓词表达式回退成
origin/main形状(primaryField || titleFormat || name-like),保留全部新断言与新文案 ⇒ 预期 (a) 与控制面用例、(d) 转红,(b)/(c)/文案断言保持绿。
实测(
packages/lint重新 build 后跑packages/cli该测试文件):与申报逐条吻合:三红三绿,红的方向与原因都是预期的那两个。回退后已按字节还原
(
git diff空),并重新 build + 全绿复跑。6. changeset 级别依据
@objectstack/lint是发布包 ⇒ 走真 changeset(非 skip 路线),级别 patch,依据仓内既有惯例:packages/lint上「新增规则」用minor(如.changeset/lint-visibility-bare-identifier-gate.md、action-body-write-set-lint.md),而「既有规则的判定/文案修正」用patch—— 同类先例.changeset/body-write-lint-message-driver-truth.md(诊断文案与运行时事实不符)与.changeset/flow-lint-loop-body-descent.md(既有规则族漏判)。本 PR 属后者:修的是与平台自身迁移建议相悖的误报,不新增规则、不改规则 id、不改 severity。v17 窗口期,未使用 major。
只声明
@objectstack/lint(@objectstack/cli侧只有测试文件变动,不发版),与flow-lint-loop-body-descent.md的先例一致。7. 一处相邻事实,已另立单不夹带(#6326)
复核提示文案时实测到:
primaryField根本不是可声明键。三点处置,刻意保守:
primaryField支保留不动(派单点 (c) 要求行为不变;摘除它有自己的爆炸半径,是
primaryField不是可声明键(ObjectSchema 以 unrecognized_keys 拒收),但两条 lint 规则与 objectstack-data 技能文档都把它当作合法 name 面 #6326 的事);schema 会硬拒的键,这是「让 AI 写的元数据难写错」这一条上的实打实的坑。新文案只点名
nameField与 name-like 字段,并由断言expect(issue?.message).not.toContain('primaryField')钉住;primaryField不是可声明键(ObjectSchema 以 unrecognized_keys 拒收),但两条 lint 规则与 objectstack-data 技能文档都把它当作合法 name 面 #6326 记录完整事实(data-model-rules.ts的死支、validate-semantic-roles.ts:190、以及
skills/objectstack-data/SKILL.md:1001那行把primaryField写成合法逃逸口的规则表),处置方向交分诊。
8. 消费半径清扫
lintDataModel的消费者:packages/cli/src/commands/lint.ts:405(os lint)。按规则消费半径而非编辑包清扫 fixture:全仓检索
titleFormat出现在测试/fixture 的位置,只有packages/lint/src/validate-record-title.test.ts(另一条规则,未动)与packages/spec的迁移登记表。没有任何 fixture 靠
titleFormat逃逸本规则,故无需 fixture 三分类处置。packages/cli/src/lint/score.ts与本规则 id / 文案零耦合(检索确认)。9. 门禁 EXIT
npx vitest run test/data-model-rules.test.ts --maxWorkers=2(packages/cli)pnpm --workspace-concurrency=2 --filter @objectstack/lint testpnpm lintpnpm --workspace-concurrency=2 --filter @objectstack/lint --filter @objectstack/cli typecheckpnpm check:nul-bytespnpm check:empty-changeset/check-changeset-no-major.mjs/check-changeset-fixed.mjspackages/cli首次 typecheck 报了 17 条 TS2307 "Cannot find module",全部是新 worktree 里未 build 的 workspace 依赖(
@objectstack/runtime/driver-sql/types…),与本 PR 文件面无关;pnpm --filter '@objectstack/cli^...' build后重跑 EXIT 0。10. 不在本 PR 里
packages/lint/src/validate-record-title.ts—— 只读对照,一字未动;packages/lint/src/validate-visibility-predicates.ts(feat(lint): view/page 谓词裸标识符构建期闸门 —— 坏谓词发不出去 (#6128) #6248 刚落)—— 未动;skills/objectstack-data/SKILL.md:1001的规则表行 —— 未动(skills 面另有在飞工作),该行的失准已并入
primaryField不是可声明键(ObjectSchema 以 unrecognized_keys 拒收),但两条 lint 规则与 objectstack-data 技能文档都把它当作合法 name 面 #6326;displayNameField支:两条独立证据都指向不补。其一,producer 侧已经规范化——ObjectSchema.parse/safeParse先过normalizeNameFieldAlias(object.zod.ts:2053-2060),nameField缺席时把displayNameField抄成nameField,所以过了 schema 的元数据到达本规则时已经带着
nameField;在 consumer 侧再加一支??别名正是 Prime Directive Add comprehensive test suite for Zod schema validation #12 要避免的。其二,实测全仓 77 个
*.object.ts,修后仍被本规则判中的 37 个里,0 个会被这一支救回;objectTitleCompleteness:那会连带改动primaryField与name-like 两支的语义(共享判定的字段合格性与候选名单都不同),属于另一次改动的爆炸半径;
primaryField支的去留 ——primaryField不是可声明键(ObjectSchema 以 unrecognized_keys 拒收),但两条 lint 规则与 objectstack-data 技能文档都把它当作合法 name 面 #6326。Generated by Claude Code