延续 #514 (已关闭,17 条里约 2/3 已完成)。本 issue 只收剩下的部分,每条都已对照 main(3ed27b26)逐一核实 ,不再需要去 #514 里考古哪些做过了。
分两类:A 组可以直接开工 ,B 组需要先定方案 。
A 组 — 可直接开工(4 条,彼此文件面不重叠,可并行)
A1. 真实 QuickJS action 执行器(原 #514 项 17,剩余部分中价值最高)
test/helpers/ 已有 flow-harness.ts 和 hook-harness.ts,但 action 侧仍是 test/global-actions.test.ts 里的简易执行器——不是真实 QuickJS 沙箱 ,所以「body-only、不得读取模块作用域」这类约束测不出来。
具体要守住的一条现存约束(目前只活在注释里):src/objects/_line-item-price-fill.ts 的共享工厂只在 handler body 从不读取工厂参数时才安全 ——一旦读取,会在编写期解析、进 QuickJS 后变成 undefined(#570 的作者自己在 PR 里 flag 了这点)。
这条的价值不止于测试覆盖:2026-07-31 当天出现过三次「PR 各自绿、合并后 main 红」,只有一次被测试自动抓住 ,另外两次(#547 悬空授权、#439 锁文件)靠人工发现。
A2. disqualification_reason 承诺了必填却零强制(原项 4)
src/objects/lead.object.ts:246 的字段 description 写着 "Required when status is Unqualified" ,但仓库里没有任何校验或 hook 实现它,也没有写入方。种子数据里有 3 条 unqualified 线索。
仓库内已有现成的实现范式:case.object.ts 用 P 校验实现了同样的「状态为 X 时必填」。
A3. fieldGroups 缺失(原项 13)
crm_campaign 和 crm_task 都是有完整详情页的对象,但 fieldGroups 计数为 0 (其余业务对象都有)。两个 line-item 对象没有可以接受。
A4. priority_rank 默认值分歧(原项 14)
crm_case.priority_rank 默认 1 ,crm_task.priority_rank 默认 2 ,且 rank 映射表在两个 hook 里各写了一份——未知优先级在两个对象上排序结果不同。注意 test/metadata-references.test.ts 已经钉住了这一带,改前先看。
B 组 — 需要先定方案(4 条,请勿直接派给 agent )
B1. 转换后线索保护:两套实现,保哪套?
lead.object.ts:311 的 validation:只锁 4 个身份字段,可恢复错误
lead.hook.ts:164 的 beforeUpdate 守卫:白名单之外全锁,抛错
注意:#514 原文说 hook「没有 ctx.user?.id 守卫」——这一条已经不成立 ,守卫已补上,注释里也写明了两者的分工意图(validation 管身份字段的友好报错,hook 管其余字段的硬拦截)。所以现在是「要不要保留这个有意的双层设计」,而不是「修一个缺陷」。选项:(a) 保留双层并把分工写进文档;(b) 合并成一套。
B2. 两个无写入方的 readonly 字段
crm_opportunity.created_date —— 平台已自动注入 created_at,建议直接删 ;代价是 opportunity.view.ts:149 的 startDateField 要改指 created_at,并清掉 zh-CN 两处翻译
crm_case.first_response_date —— 属于 SLA 叙事的一部分。这条是业务决策 :服务团队真要这个指标 → 去掉 readonly 并在 case.hook 里接写入方(首次对外活动时打戳);没人看 → 删
B3. 40 个 changeset 堆积,无人消费
.changeset/ 下有 40 个待发布条目,没有任何 workflow 跑 changeset version/publish。要么定发布流程(打 tag 时跑),要么手工折进 CHANGELOG 并移除机制。改错会影响版本号,建议先跟发布节奏对齐。
B4. state machine 覆盖不全(feature-shaped)
15 个业务对象里只有 3 个(lead / opportunity / case)声明了 type: 'state_machine' 转换表;quote / contract / campaign / task 都有状态字段但没有转换约束。补不补是产品决策,不是缺陷修复。
给接手者的约定(2026-07-31 踩出来的)
动手前先核实问题仍存在 —— 本 issue 已核实到 3ed27b26,但 main 移动很快
新守卫写进新建的测试文件 ,不要追加到 test/metadata-references.test.ts(当天的高频冲突文件)
提交信息用 Refs,不要用 Closes/Fixes —— 除非该 PR 完整解决本 issue。[CRM] 9 个孤儿 __search 列待清理(17.0 收紧搜索伴生列供给条件后遗留) #528 当天就是被提交信息里的 Closes 误关的,改 PR 描述拦不住
必须附 changeset ,否则 CI 的 Check Changeset 会红
合并后在 main 上复跑一次 pnpm verify —— 当天三次「合并时绿、落 main 才红」
延续 #514(已关闭,17 条里约 2/3 已完成)。本 issue 只收剩下的部分,每条都已对照
main(3ed27b26)逐一核实,不再需要去 #514 里考古哪些做过了。分两类:A 组可以直接开工,B 组需要先定方案。
A 组 — 可直接开工(4 条,彼此文件面不重叠,可并行)
A1. 真实 QuickJS action 执行器(原 #514 项 17,剩余部分中价值最高)
test/helpers/已有flow-harness.ts和hook-harness.ts,但 action 侧仍是test/global-actions.test.ts里的简易执行器——不是真实 QuickJS 沙箱,所以「body-only、不得读取模块作用域」这类约束测不出来。具体要守住的一条现存约束(目前只活在注释里):
src/objects/_line-item-price-fill.ts的共享工厂只在 handler body 从不读取工厂参数时才安全——一旦读取,会在编写期解析、进 QuickJS 后变成undefined(#570 的作者自己在 PR 里 flag 了这点)。这条的价值不止于测试覆盖:2026-07-31 当天出现过三次「PR 各自绿、合并后 main 红」,只有一次被测试自动抓住,另外两次(#547 悬空授权、#439 锁文件)靠人工发现。
A2.
disqualification_reason承诺了必填却零强制(原项 4)src/objects/lead.object.ts:246的字段 description 写着 "Required when status is Unqualified",但仓库里没有任何校验或 hook 实现它,也没有写入方。种子数据里有 3 条unqualified线索。仓库内已有现成的实现范式:
case.object.ts用P校验实现了同样的「状态为 X 时必填」。A3.
fieldGroups缺失(原项 13)crm_campaign和crm_task都是有完整详情页的对象,但fieldGroups计数为 0(其余业务对象都有)。两个 line-item 对象没有可以接受。A4.
priority_rank默认值分歧(原项 14)crm_case.priority_rank默认 1,crm_task.priority_rank默认 2,且 rank 映射表在两个 hook 里各写了一份——未知优先级在两个对象上排序结果不同。注意test/metadata-references.test.ts已经钉住了这一带,改前先看。B 组 — 需要先定方案(4 条,请勿直接派给 agent)
B1. 转换后线索保护:两套实现,保哪套?
lead.object.ts:311的 validation:只锁 4 个身份字段,可恢复错误lead.hook.ts:164的 beforeUpdate 守卫:白名单之外全锁,抛错注意:#514 原文说 hook「没有
ctx.user?.id守卫」——这一条已经不成立,守卫已补上,注释里也写明了两者的分工意图(validation 管身份字段的友好报错,hook 管其余字段的硬拦截)。所以现在是「要不要保留这个有意的双层设计」,而不是「修一个缺陷」。选项:(a) 保留双层并把分工写进文档;(b) 合并成一套。B2. 两个无写入方的 readonly 字段
crm_opportunity.created_date—— 平台已自动注入created_at,建议直接删;代价是opportunity.view.ts:149的startDateField要改指created_at,并清掉 zh-CN 两处翻译crm_case.first_response_date—— 属于 SLA 叙事的一部分。这条是业务决策:服务团队真要这个指标 → 去掉readonly并在case.hook里接写入方(首次对外活动时打戳);没人看 → 删B3. 40 个 changeset 堆积,无人消费
.changeset/下有 40 个待发布条目,没有任何 workflow 跑changeset version/publish。要么定发布流程(打 tag 时跑),要么手工折进 CHANGELOG 并移除机制。改错会影响版本号,建议先跟发布节奏对齐。B4. state machine 覆盖不全(feature-shaped)
15 个业务对象里只有 3 个(lead / opportunity / case)声明了
type: 'state_machine'转换表;quote / contract / campaign / task 都有状态字段但没有转换约束。补不补是产品决策,不是缺陷修复。给接手者的约定(2026-07-31 踩出来的)
3ed27b26,但 main 移动很快test/metadata-references.test.ts(当天的高频冲突文件)Refs,不要用Closes/Fixes—— 除非该 PR 完整解决本 issue。[CRM] 9 个孤儿 __search 列待清理(17.0 收紧搜索伴生列供给条件后遗留) #528 当天就是被提交信息里的Closes误关的,改 PR 描述拦不住pnpm verify—— 当天三次「合并时绿、落 main 才红」