Skip to content

fix(lint): object/missing-name-field 认 nameField、不再采信已退役的 titleFormat (#6108) - #6338

Merged
hotlong merged 1 commit into
mainfrom
claude/issue-6108-name-field-predicate
Aug 7, 2026
Merged

fix(lint): object/missing-name-field 认 nameField、不再采信已退役的 titleFormat (#6108)#6338
hotlong merged 1 commit into
mainfrom
claude/issue-6108-name-field-predicate

Conversation

@hotlong

Copy link
Copy Markdown
Contributor

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 读到的完全一致:

// R9 — object should have a derivable display/primary field.consthasNameField=!!obj.primaryField||!!obj.titleFormat||fields.some((f)=>NAME_LIKE_FIELDS.includes(f.name));

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-97每一处非空声明都报 title-format-retired(warning),文案 "titleFormat is retired (ADR-0079) — migrate to nameField"
packages/spec/src/data/display-name.ts:311-318(objectTitleCompleteness)判定链是 nameField ?? displayNameField ?? 派生,从不读titleFormat
packages/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-68迁移条目 object-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-retiredtitle-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-sharenotification-delivery)。
这 22 个正是 title-unresolvable 今天已经在报的同一批——两条规则从「互相矛盾」收敛到「重复确认」。

3. 谓词 before / after

before: hasNameField = !!obj.primaryField || !!obj.titleFormat || fields.some(name-like)
after: hasNameField = !!obj.nameField || !!obj.primaryField || fields.some(name-like)

primaryField 与 name-like 两支逐字未动。提示文案改为只点名作者真正能声明的面,并新增
fix 提示(见 §7 第 3 条)。

4. hotcrm 对照面 → fixture 映射(以 fixture 复刻,不跨仓)

新增 describe('lintDataModel — object/missing-name-field (ADR-0079 title face)'),
六条断言。核心一条把 issue 正文那张表整个喂进 lintDataModel,断言只剩两个 line-item 被报:

hotcrm 对象nameField本 PR fixture修前修后
crm_campaign_membermember_number控制面用例 objects[0]误报不报
crm_event_attendeeattendee_number控制面用例 objects[1]误报不报
crm_contractcontract_number控制面用例 objects[2] + 独立用例 (a)误报不报
crm_forecastdisplay_title控制面用例 objects[3]误报不报
crm_opportunity_line_item(无)控制面用例 objects[4]真命中仍报
crm_quote_line_item(无)控制面用例 objects[5] + 独立用例 (b)真命中仍报

控制面断言写成 expect(flagged(objects)).toEqual(['objects[4].fields', 'objects[5].fields'])
——用完整路径列表而不是计数,以免「因为什么都没产生所以断言通过」的空绿。

另外三条:

  • (c) 两支未动路径,彼此隔离取样(crm_forecast_period 只有 primaryField,
    crm_campaign 只有 name-like 的 name;既有那条 primaryField: 'code' 用例里
    code 本身就是 name-like,两支互相遮蔽,故另取样本);
  • (d) titleFormat-only ⇒ 修后告警,断言注释里写明这是刻意翻转而非回归,并指向
    validate-record-title 的双报;
  • 文案断言:提示必须点名 nameField,fix 必须同时出现 ADR-0079titleFormat

5. 反向验证(先申报,后执行,两段对照)

申报(执行前写定):只把谓词表达式回退成 origin/main 形状(primaryField || titleFormat || name-like),
保留全部新断言与新文案 ⇒ 预期 (a) 与控制面用例、(d) 转红,(b)/(c)/文案断言保持绿。

实测(packages/lint 重新 build 后跑 packages/cli 该测试文件):

 × accepts an object whose title face is an explicit nameField 9ms
× suggests a name field for a titleFormat-only object (retired key is not a title face) 1ms
× reproduces the hotcrm control surface: 6 objects in, only the 2 line items flagged 3ms
AssertionError: expected true to be false <- (a) 误报重现
AssertionError: expected false to be true <- (d) 退役字段仍被采信
AssertionError: expected [ 'objects[0].fields', …(5) ] to deeply equal [ 'objects[4].fields', …(1) ]
<- 控制面 6 个全报,而非只报 2 个
Test Files 1 failed (1)
Tests 3 failed | 48 passed (51)

与申报逐条吻合:三红三绿,红的方向与原因都是预期的那两个。回退后已按字节还原
(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 根本不是可声明键

ObjectSchema.safeParse({name: 'probe_obj',label: 'Probe',primaryField: 'code',fields: {code: {type: 'text',label: 'Code'}}});// => success: false ; issues: [{ code: 'unrecognized_keys', keys: ['primaryField'], path: [] }]ObjectSchema.create(/* 同上 */);// => throws: ObjectSchema.create('probe_obj'): unknown key(s) — primaryField.

三点处置,刻意保守:

  1. 谓词里的 primaryField 支保留不动(派单点 (c) 要求行为不变;摘除它有自己的爆炸半径,
    primaryField 不是可声明键(ObjectSchema 以 unrecognized_keys 拒收),但两条 lint 规则与 objectstack-data 技能文档都把它当作合法 name 面 #6326 的事);
  2. 提示文案不再点名它 —— 旧文案 "has no name/title field or primaryField" 等于教作者去写一个
    schema 会硬拒的键,这是「让 AI 写的元数据难写错」这一条上的实打实的坑。新文案只点名
    nameField 与 name-like 字段,并由断言 expect(issue?.message).not.toContain('primaryField') 钉住;
  3. 另立 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

门禁命令EXIT
规则单测npx vitest run test/data-model-rules.test.ts --maxWorkers=2(packages/cli)0 — 51 passed (51)
packages/lint 全量单测pnpm --workspace-concurrency=2 --filter @objectstack/lint test0 — 61 files / 1504 tests passed
eslintpnpm lint0
typecheckpnpm --workspace-concurrency=2 --filter @objectstack/lint --filter @objectstack/cli typecheck0
控制字节pnpm check:nul-bytes0 — 5966 tracked files,无裸控制字节
changesetpnpm check:empty-changeset / check-changeset-no-major.mjs / check-changeset-fixed.mjs0 / 0 / 0

packages/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 里


Generated by Claude Code

…#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
@vercel

vercelBot commented Aug 7, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
objectstackIgnoredIgnoredAug 7, 2026 2:10pm

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/lint.

3 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/automation/hook-bodies.mdx(via @objectstack/lint)
  • content/docs/permissions/authorization.mdx(via @objectstack/lint)
  • content/docs/releases/v17.mdx(via @objectstack/lint)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

2 participants

@hotlong@claude