#4535 的 C12 簇,v17 收口最后三簇之一 。基线行:
FieldMapping — [./data (type)] ≠ [./integration (type)] ≠ [./shared (type)]
FieldMappingSchema — [./data (const)] ≠ [./integration (const)] ≠ [./shared (const)]
基线现为 16 条 (C9 刚落地,c1f344b),本簇目标 16 → 14 。
定位表(PM 已对 post-C9 的 origin/main 复核,开工请自验)
侧
声明
形态
authorable key
./shared
shared/mapping.zod.ts:102
基 ,plain z.object
4 :source target transform defaultValue
./integration
integration/connector.zod.ts:105
BaseFieldMappingSchema.extend({…})(:7 以别名 import 基)
7 :基 4 + dataType required syncMode
./data
data/mapping.zod.ts:97
独立 strictObject,不 extend 基
4 :source target transform params
⚠️ 这不是「同一概念三种拼法」—— 三侧是两个不同概念
./shared 与 ./integration 是基与其超集 (extend,7 ⊃ 4)。但 ./data 是另一件事 :它是数据导入的列映射 (CSV/表格 → 对象字段),不是 connector 同步映射。三处硬证据:
transform 同名不同类型 —— ./shared+./integration 用判别联合 FieldMappingTransformSchema(shared/mapping.zod.ts:52);./data 用普通枚举 TransformType 加 .default('none'),配一个平铺的 params 袋子。同一个键名,类型互不兼容。这条是三侧不能共名的最硬证据。
source / target 基数不同 —— ./data 是 z.union([z.string(), z.array(z.string())])(一个目标字段可由多列合成);另两侧只收 string。
./data 是 strictObject,另两侧不是 —— 它带 surface / history / aliases(未知键静默剥离仍是全仓默认:把 #3405 的 strict 收紧从一个 schema 推广到整个可授权面(ADR-0078 完整性闸门) #4001 的别名归一 + 拒绝未知键机制,from/sourceField/column/header → source 等)。该侧未知键会 throw,不是静默 strip ,与另两侧的失败模式相反。
推荐路线(PM 分析;复核结论若与此矛盾请升级而不是硬做)
按 ADR-0112 D9(a) 、C9(#4684 / PR #4695 )刚刚确立的先例 办:./shared 保留基名不动,给两个领域专用侧加领域前缀 。
integration/FieldMapping → ConnectorFieldMapping (带 7 个 key)
data/FieldMapping → ImportFieldMapping (带 4 个 key)
这个命名不是新发明 —— 同一仓里已经是这个风格 :data/external-lookup.zod.ts:86 的 ExternalFieldMappingSchema 就是 BaseFieldMappingSchema.extend(…) 加领域前缀,且正因为加了前缀,它从来没进过基线 。C9 也刚把 integration/RateLimitConfig 改成 ConnectorRateLimitConfig,同文件更早还有 ConnectorErrorCategory / ConnectorRetryStrategy。
🔑 本簇是 C9 新建的 RENAMED_DEFS 承接表的第一个真实消费者
C9(PR #4695 )为解 def 改名的门禁死结新建了 packages/spec/scripts/lib/renamed-defs.ts,并接进了 build-schemas.ts 的两道 ratchet。本簇直接用它,不要另造轮子,更不要手编基线。 加两条:
'integration/FieldMapping' : 'integration/ConnectorFieldMapping' ,
'data/FieldMapping' : 'data/ImportFieldMapping' ,
承接表已强制三条不变式(旧 def 每个 key 必须在新 def 下存在 / target 必须被产出 / source 必须不再被产出),并保留旧 key 的 retired 状态。先读那个文件的文档注释再动手 —— 它把为什么不能走 tombstone / conversion 讲清楚了。
注意 :这是承接表第一次同时承接两个 def,也是第一次承接别处 extend 的基 。若你发现承接逻辑在多条目或 extend 场景下有洞,那本身就是有价值的交付物 —— 修它并补单测(packages/spec/scripts/renamed-defs.test.ts 已有 10 条规则单测可扩)。
⚠️ 必须同步清空 C9 留下的显式清单
packages/spec/src/integration/connector.test.ts:790:
const KNOWN_STILL_DUAL_SOURCE = [ 'FieldMapping' , 'FieldMappingSchema' ] ;
那是 C9 通用不变式 pin 里故意 写成显式清单的「已知尚存双源」(这样新出现一个就红,而不是写一条从来不成立的「不许同名」)。本簇清完后必须把它改成 [],否则该 pin 会红。 这是设计好的强制握手,不是遗漏。
纪律(#4535 §1–§8 + 手册,开工前必读)
⛔ 禁止手编 packages/spec/authorable-surface.json (authorable-surface 的 tombstone 门禁可被手编基线绕过 —— 删掉基线行就删掉了证据(#4638 / #4643 已两次这样过绿) #4650 )。用 gen:schema 经承接表重新生成。
⚠️ 门禁绿 ≠ 登记正确 (build-schemas.ts 检查 (b) 用叶名匹配 conversion surface —— 无关簇的 .type 就能让一个 tombstone 冒充「已登记迁移」 #4659 ):检查 (b) 按 leaf name 匹配 conversion surface,自己枚举核对。
零 tombstone、零 ADR-0087 conversion —— 改名不 retire 任何 key。产生了就是走偏,停下升级。
回归 pin 必须能真的红并 sabotage 验证。 packages/spec 的「编译期 pin」是失效的:tsconfig 排除了 *.test.ts,vitest 也不做类型检查 #4642 已证本包编译期 pin 空转 (tsconfig.json 排除 **/*.test.ts、vitest 不开 typecheck)。FieldMapping 是类型 ,运行时看不见 —— 抄 PR refactor(spec)!: 双源清账 C9 — connector 侧 RateLimitConfig 改名 + ratchet 学会承接 def 改名 (#4684) #4695 / refactor(spec): 双源 C11 收敛 — HttpRequest 类型别名改为 re-export ./shared 的唯一声明 (#4688) #4689 里那条 TypeScript compiler API 符号身份解析 断言(含防空转守卫 expect(moduleSym).toBeTruthy() 与 origins.size > 20)。本簇是三源,pin 要覆盖三个入口两两之间。
额外钉住概念差异 :./data 侧 transform 是枚举 + params 袋子、source/target 收数组、且 strictObject 未知键 throw ;另两侧 transform 是判别联合、只收 string、未知键静默 strip。这些差异正是三侧不能共名的理由,钉住它们防止将来有人「顺手统一」。
./data 侧动了 strictObject 就要同步严格性台账 docs/audits/2026-07-unknown-key-strictness-ledger.md(该文件 :475 有 mapping.zod.ts 条目)。纯改名不应改变严格性,但必须实际跑 check:strictness-ledger 确认 ,别假设。
⚠️ 生成物冲突只能靠重新生成 (手册第 7 条)。推之前重新 git fetch origin main;spec-changes.json 是对象数组,必须跑 gen:spec-changes ,集合合并会丢条目。
⛔ 不要碰 content/docs/releases/ 。
注意 gen:docs 可能挪动 content/docs/references/** 的页面归属 —— 见 build-docs.ts 的 schema→页面索引按「裸名字」全局建表,同名跨 category 的 schema 会被归到错误的页面 #4696 (build-docs.ts 按裸 schema 名做全局索引,同名跨 category 互相覆盖)。FieldMapping 正是 build-docs.ts 的 schema→页面索引按「裸名字」全局建表,同名跨 category 的 schema 会被归到错误的页面 #4696 点名的高危同名对之一 ,改名后文档页归属很可能变化。那是修复不是回归 ,但请在 PR 里说明,别当成意外。
验收
基线删掉上面 2 行,16 → 14 ,其余 14 行一字不动 。
./shared 侧的 FieldMapping / FieldMappingSchema 原样不动 。
RENAMED_DEFS 两条,7 + 4 个 key 逐条核对表 ,一个不少。
connector.test.ts 的 KNOWN_STILL_DUAL_SOURCE 清空为 []。
三源 pin + 概念差异 pin,全部 sabotage 验证并贴实际输出 。
零 tombstone、零 ADR-0087 conversion。
全绿:build、check:dual-source-exports、check:generated、test,加源码审计组(check:liveness / check:strictness-ledger / check:empty-state / check:variant-docs / check:exported-any / check:skill-examples),以及全仓 pnpm typecheck。
changeset 一份,@objectstack/spec major ,含两条 FROM → TO 的 import 改法、$id 迁移、以及「作者写的元数据是否需要迁移」的明确结论(据实写 :C9 是 major 但元数据零迁移,C11 是 patch;不要照抄任一结论,也不要为凑级别夸大 )。
⛔ 不碰 content/docs/releases/,不手编 authorable-surface.json。
判不出来就升级
若你复核后认为 ./shared 与 ./integration 应该收敛 而不是改名(它们确实是基与超集),或认为 ImportFieldMapping / ConnectorFieldMapping 的命名不妥,停下来把多轴分析写进报告 —— #4653 / #4658 / #4661 三簇都是这样处理的,分析本身就是交付物。
关联:#4535 (主单)、#4684 / PR #4695 (C9,RENAMED_DEFS 本体与先例)、#4688 / PR #4689 (C11,类型级 pin 形态)、#4696 (docs 生成器按裸名索引)、#4650 / #4659 / #4666 (门禁洞)、#4642 (pin 空转)、#4675 (生成物冲突)、#4001 (strictObject 别名机制)、ADR-0049、ADR-0087、ADR-0104、ADR-0112
#4535 的 C12 簇,v17 收口最后三簇之一。基线行:
基线现为 16 条(C9 刚落地,
c1f344b),本簇目标 16 → 14。定位表(PM 已对 post-C9 的
origin/main复核,开工请自验)./sharedshared/mapping.zod.ts:102z.objectsourcetargettransformdefaultValue./integrationintegration/connector.zod.ts:105BaseFieldMappingSchema.extend({…})(:7以别名 import 基)dataTyperequiredsyncMode./datadata/mapping.zod.ts:97strictObject,不 extend 基sourcetargettransformparams./shared与./integration是基与其超集(extend,7 ⊃ 4)。但./data是另一件事:它是数据导入的列映射(CSV/表格 → 对象字段),不是 connector 同步映射。三处硬证据:transform同名不同类型 ——./shared+./integration用判别联合FieldMappingTransformSchema(shared/mapping.zod.ts:52);./data用普通枚举TransformType加.default('none'),配一个平铺的params袋子。同一个键名,类型互不兼容。这条是三侧不能共名的最硬证据。source/target基数不同 ——./data是z.union([z.string(), z.array(z.string())])(一个目标字段可由多列合成);另两侧只收string。./data是strictObject,另两侧不是 —— 它带surface/history/aliases(未知键静默剥离仍是全仓默认:把 #3405 的 strict 收紧从一个 schema 推广到整个可授权面(ADR-0078 完整性闸门) #4001 的别名归一 + 拒绝未知键机制,from/sourceField/column/header→source等)。该侧未知键会 throw,不是静默 strip,与另两侧的失败模式相反。推荐路线(PM 分析;复核结论若与此矛盾请升级而不是硬做)
按 ADR-0112 D9(a)、C9(#4684 / PR #4695)刚刚确立的先例办:
./shared保留基名不动,给两个领域专用侧加领域前缀。integration/FieldMapping→ConnectorFieldMapping(带 7 个 key)data/FieldMapping→ImportFieldMapping(带 4 个 key)这个命名不是新发明 —— 同一仓里已经是这个风格:
data/external-lookup.zod.ts:86的ExternalFieldMappingSchema就是BaseFieldMappingSchema.extend(…)加领域前缀,且正因为加了前缀,它从来没进过基线。C9 也刚把integration/RateLimitConfig改成ConnectorRateLimitConfig,同文件更早还有ConnectorErrorCategory/ConnectorRetryStrategy。🔑 本簇是 C9 新建的
RENAMED_DEFS承接表的第一个真实消费者C9(PR #4695)为解 def 改名的门禁死结新建了
packages/spec/scripts/lib/renamed-defs.ts,并接进了build-schemas.ts的两道 ratchet。本簇直接用它,不要另造轮子,更不要手编基线。 加两条:承接表已强制三条不变式(旧 def 每个 key 必须在新 def 下存在 / target 必须被产出 / source 必须不再被产出),并保留旧 key 的 retired 状态。先读那个文件的文档注释再动手 —— 它把为什么不能走 tombstone / conversion 讲清楚了。
注意:这是承接表第一次同时承接两个 def,也是第一次承接别处 extend 的基。若你发现承接逻辑在多条目或 extend 场景下有洞,那本身就是有价值的交付物 —— 修它并补单测(
packages/spec/scripts/renamed-defs.test.ts已有 10 条规则单测可扩)。packages/spec/src/integration/connector.test.ts:790:那是 C9 通用不变式 pin 里故意写成显式清单的「已知尚存双源」(这样新出现一个就红,而不是写一条从来不成立的「不许同名」)。本簇清完后必须把它改成
[],否则该 pin 会红。 这是设计好的强制握手,不是遗漏。纪律(#4535 §1–§8 + 手册,开工前必读)
packages/spec/authorable-surface.json(authorable-surface 的 tombstone 门禁可被手编基线绕过 —— 删掉基线行就删掉了证据(#4638 / #4643 已两次这样过绿) #4650)。用gen:schema经承接表重新生成。.type就能让一个 tombstone 冒充「已登记迁移」 #4659):检查 (b) 按 leaf name 匹配 conversion surface,自己枚举核对。tsconfig.json排除**/*.test.ts、vitest 不开typecheck)。FieldMapping是类型,运行时看不见 —— 抄 PR refactor(spec)!: 双源清账 C9 — connector 侧 RateLimitConfig 改名 + ratchet 学会承接 def 改名 (#4684) #4695 / refactor(spec): 双源 C11 收敛 — HttpRequest 类型别名改为 re-export ./shared 的唯一声明 (#4688) #4689 里那条 TypeScript compiler API 符号身份解析断言(含防空转守卫expect(moduleSym).toBeTruthy()与origins.size > 20)。本簇是三源,pin 要覆盖三个入口两两之间。./data侧transform是枚举 +params袋子、source/target收数组、且strictObject未知键 throw;另两侧transform是判别联合、只收 string、未知键静默 strip。这些差异正是三侧不能共名的理由,钉住它们防止将来有人「顺手统一」。./data侧动了strictObject就要同步严格性台账docs/audits/2026-07-unknown-key-strictness-ledger.md(该文件:475有mapping.zod.ts条目)。纯改名不应改变严格性,但必须实际跑check:strictness-ledger确认,别假设。git fetch origin main;spec-changes.json是对象数组,必须跑gen:spec-changes,集合合并会丢条目。content/docs/releases/。gen:docs可能挪动content/docs/references/**的页面归属 —— 见 build-docs.ts 的 schema→页面索引按「裸名字」全局建表,同名跨 category 的 schema 会被归到错误的页面 #4696(build-docs.ts按裸 schema 名做全局索引,同名跨 category 互相覆盖)。FieldMapping正是 build-docs.ts 的 schema→页面索引按「裸名字」全局建表,同名跨 category 的 schema 会被归到错误的页面 #4696 点名的高危同名对之一,改名后文档页归属很可能变化。那是修复不是回归,但请在 PR 里说明,别当成意外。验收
./shared侧的FieldMapping/FieldMappingSchema原样不动。RENAMED_DEFS两条,7 + 4 个 key 逐条核对表,一个不少。connector.test.ts的KNOWN_STILL_DUAL_SOURCE清空为[]。build、check:dual-source-exports、check:generated、test,加源码审计组(check:liveness/check:strictness-ledger/check:empty-state/check:variant-docs/check:exported-any/check:skill-examples),以及全仓pnpm typecheck。@objectstack/specmajor,含两条 FROM → TO 的 import 改法、$id迁移、以及「作者写的元数据是否需要迁移」的明确结论(据实写:C9 是major但元数据零迁移,C11 是patch;不要照抄任一结论,也不要为凑级别夸大)。content/docs/releases/,不手编authorable-surface.json。判不出来就升级
若你复核后认为
./shared与./integration应该收敛而不是改名(它们确实是基与超集),或认为ImportFieldMapping/ConnectorFieldMapping的命名不妥,停下来把多轴分析写进报告 —— #4653 / #4658 / #4661 三簇都是这样处理的,分析本身就是交付物。关联:#4535(主单)、#4684 / PR #4695(C9,
RENAMED_DEFS本体与先例)、#4688 / PR #4689(C11,类型级 pin 形态)、#4696(docs 生成器按裸名索引)、#4650 / #4659 / #4666(门禁洞)、#4642(pin 空转)、#4675(生成物冲突)、#4001(strictObject 别名机制)、ADR-0049、ADR-0087、ADR-0104、ADR-0112