refactor(spec)!: 双源清账 C12 — FieldMapping 三源改名为 ConnectorFieldMapping / ImportFieldMapping (#4703) - #4710
Merged
Merged
Conversation
…, C12) `FieldMapping` / `FieldMappingSchema` were published by THREE entry points for three different declarations — the #4411 trap, one entry worse than the usual pair: ./shared the base, plain z.object, 4 keys ./integration Base.extend({ dataType, required, syncMode }), 7 keys ./data an independent strictObject, 4 keys — and a different CONCEPT: the column mapping of a CSV/table import, not a connector's remote-field mapping Per ADR-0112 D9(a) the two domain-specific sides take a domain prefix and the base keeps the bare name: integration/FieldMapping -> integration/ConnectorFieldMapping data/FieldMapping -> data/ImportFieldMapping `shared/FieldMapping` is untouched — two other defs extend it, including `data/ExternalFieldMapping`, which has never been in the baseline precisely because it already carries a domain prefix. dual-source-exports baseline: 16 -> 14. Zero tombstones and zero ADR-0087 conversions: all eleven authorable keys (7 + 4) carry over unchanged, so no authored metadata migrates. The rename is carried through the two def-keyed ratchets by `RENAMED_DEFS` (#4684), which gets its first entries beyond the original one — and with them the first two rules that only bind when the table holds more than one entry: two sources onto one target is rejected as a merge (it would collapse the carried key sets and their retired states, blinding the "live -> retired needs a conversion" check), and a chained rename is rejected by name rather than misdiagnosed as a typo. Regression pins live in `src/integration/connector.test.ts` next to the #4684 block: a TypeScript compiler-API symbol-identity resolution over all three pairs of entries (types are erased at runtime, and #4642 proved a compile-time pin in this package is dead text), plus three runtime pins on the concept differences that justify the rename — `transform`'s union-vs-enum split, `./data`'s array cardinality, and its strictObject throwing where the other two silently strip. The C9 `KNOWN_STILL_DUAL_SOURCE` handshake list is now empty. `gen:docs` moves the connector field mapping from a phantom `references/integration/mapping` page into `references/integration/connector`, where the schema actually lives — #4696's bare-name global index resolving now that the names are distinct. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0176qgxgCXTJCUv4YFLtusP9
…ld-mapping-tri-source
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 1 package(s): 107 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
This was referenced Aug 2, 2026
os-zhuang
marked this pull request as ready for review
August 2, 2026 20:12
os-zhuang
enabled auto-merge
August 2, 2026 20:13
os-zhuang
disabled auto-merge
August 2, 2026 20:14
os-zhuang
enabled auto-merge
August 2, 2026 20:14
This was referenced Aug 2, 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 #4703
#4535的 C12 簇。基线 16 → 14,其余 14 行一字未动。复核结论:三侧确实是两个概念,改名而非收敛
PM 的定位表我对 post-C9 的
origin/main逐条自验过,完全属实:./sharedshared/mapping.zod.ts:102z.object./integrationintegration/connector.zod.ts:105Base.extend({dataType, required, syncMode})./datadata/mapping.zod.ts:97strictObject./shared与./integration确实是基与超集,所以「该不该收敛」是个真问题。我的结论是不能,两个方向都不行:automation/sync.zod.ts和data/external-lookup.zod.ts也 extend 这个基,等于把 connector 的同步语义(syncMode/required/dataType)推给 ETL 同步和外部查找;而
./data根本不是同一个概念。三条硬证据我都实测并钉住了:transform同名不同类型 ——./shared+./integration收判别联合{ type: 'cast', targetType };./data收TransformType普通枚举 + 平铺params袋子,默认'none'。互相都解析不过。./data的source/target收string | string[](一个目标字段可由多列split/join合成),另两侧只收string。./data是strictObject(未知键静默剥离仍是全仓默认:把 #3405 的 strict 收紧从一个 schema 推广到整个可授权面(ADR-0078 完整性闸门) #4001),未知键 throw 并给出别名/拼写处方;另两侧是 plainz.object,静默 strip。同一个 typo,一边硬报错一边什么都不做。按 ADR-0112 D9(a):基保留裸名,两个领域专用侧加领域前缀。这不是新发明 —— 同仓
data/external-lookup.zod.ts:86的ExternalFieldMappingSchemaextend 的是同一个基,正因为带前缀,它从来没进过基线。11 个 key 逐条核对(4 + 7,一个不少)
authorable-surface.json总量 8265 → 8265,只有 11 行改名,没有任何增减:integration/ConnectorFieldMappingsourcetargettransformdefaultValuedataTyperequiredsyncModedata/ImportFieldMappingsourcetargettransformparamsshared/FieldMappingsourcetargettransformdefaultValue零 tombstone、零 ADR-0087 conversion ——
check:spec-changes全程绿,没有任何 key 离开契约。承接表:发现并修了两个只在「多条目」下才成立的洞
本簇是
RENAMED_DEFS(#4684)的第一个真实消费者,也是它第一次同时装两条。装上之后有两条规则才开始有意义,原表都没有:洞 1 —— 两个 source 指向同一个 target,
checkRenameTable不报错。 这不是两次改名,是一次合并,而且它恰好击穿承接表存在的理由:build-schemas.ts把快照 carry 进一个按新 key 索引的prevmap,于是两个 def 下同名属性的条目会塌成一条 —— 活下来的[RETIRED]状态是最后 carry 的那个。一个在 A 下是 live、在 B 下已 tombstone 的 key,合并后读作「早就退役了」,检查 (b)(每个 live → retired 都要有已注册的 conversion)就永远不会为它触发。那正是「承接表替一个 key 离开契约背书」,是它唯一不许干的事。现在直接拒。洞 2 —— 链式改名(A → B → C)诊断错误。 原先它已经会红,但报的是「B 没被产出」,把链误诊成拼写错误 —— 而按那个提示去改(删掉 A → B)会让 A 的 key 真的消失。carry 是单趟的,链本来就不支持,现在按名报出来。
两条都在
scripts/renamed-defs.test.ts补了单测(该文件 10 → 17 条),另加一条「不要把 extend 的基误判成 rename source」的用例,以及一条拿真实验证器跑 committed 表自身的自洽检查。extend 场景本身没有洞:target 的 key 集是基的超集,carry 的每个 key 都找得到;基自己既不是 source 也不是 target,原样产出。
回归 pin:三源,三对入口两两覆盖,全部 sabotage 验过
#4642 已证本包编译期 pin 空转(
tsconfig.json排除**/*.test.ts,vitest 不开typecheck),而FieldMapping是类型,运行时看不见。所以照 PR #4695 / #4689 用 TypeScript compiler API 做符号身份解析,含两道防空转守卫(expect(moduleSym).toBeTruthy()、origins.size > 20)。C9 那条通用不变式 pin 的
KNOWN_STILL_DUAL_SOURCE已按设计清空为[]。sabotage 实测输出
1.
./integration重新加回export type FieldMapping(旧名兼容别名——本簇明令禁止):C9 的清单 pin 和新的三源 pin 同时变红 —— 那个强制握手是活的。
2.
./data重新加回FieldMapping类型 + schema 别名:3. 概念差异 pin —— 把
./data的transform「顺手统一」到共享判别联合:4. 把
./data的source/target收窄成单个 string:5. 把
./data的strictObject改回z.object:6. 防空转守卫本身(把
./data入口指向不存在的文件):承接表的 sabotage(对改名前的快照跑,否则表已对新快照惰性)
7a. 两条承接都在 —— 只报「新增未记录」,没有任何 key 丢失,正好 11 条:
7b. 删掉
data/FieldMapping那条承接:7c. 声明了改名,却把
syncMode从新 def 里删掉(检查 (a0)):7d. 旧名留着不删(是拷贝,不是改名):
预期的「非回归」变化:文档页归属(#4696)
gen:docs删掉了content/docs/references/integration/mapping.mdx,内容并入integration/connector.mdx。这是修复,不是回归,机制已定位到行:build-docs.ts:136的schemaZodFileMap是一个跨 category 的全局 map,按裸 schema 名索引。FieldMapping被写了三次(data/mapping.zod.ts、shared/mapping.zod.ts两个 slug 都是mapping,integration/connector.zod.tsslug 是connector),最后写入者胜出 →'mapping'。所以发射integration分类时,integration/FieldMapping.json查到的 slug 是mapping,被写进了content/docs/references/integration/mapping.mdx—— 而packages/spec/src/integration/下根本没有mapping.zod.ts(该目录只有connector*.ts),那是个查无此文件的幽灵页(所以它连Source:行都没有)。名字不再撞车后,ConnectorFieldMapping正确落到它真正所在的connector.mdx。顺手改了一处手写文档
content/docs/getting-started/quick-reference.mdx:28(FieldMapping→ImportFieldMapping),它直接点名了被改的导出。:217的./shared那行本来就正确,未动。changeset:
major,但作者写的元数据零迁移据实定级,没有照抄 C9 或 C11:
major—— 两个 entry 的 TS 导出真的改名了,import { FieldMappingSchema } from '@objectstack/spec/data'会编译失败。connectors[].fieldMappings[]和mapping.fieldMapping[]里已有的 stack metadata /sys_metadata行 / 已发布应用逐字节不受影响。所以无 tombstone、无 conversion。$id迁移:…/integration/FieldMapping.json→…/integration/ConnectorFieldMapping.json,…/data/FieldMapping.json→…/data/ImportFieldMapping.json。changeset 里额外写了一条升级陷阱:编译报错后不要把 import 改指
@objectstack/spec/shared。那个名字解析得过去,但拿到的是基——connector 侧会静默丢掉dataType/required/syncMode(基不是.strict(),parse 时直接 strip),import 侧则会直接拒掉数组和枚举形式的transform。门禁实测
pnpm --filter @objectstack/spec buildcheck:generated(8 项)✓ All 8 generated artifacts are up to date.check:dual-source-exports✅ no new dual-source exports: 4328 names across 16 entry points — 166 re-exported, 14 accepted dual-source (baseline).pnpm --filter @objectstack/spec testTest Files 293 passed (293) / Tests 7374 passed (7374)pnpm --filter @objectstack/spec typecheckcheck:strictness-ledger✓ 67 file(s) across 5 triaged directories — site counts matchcheck:liveness✓ all governed-type properties are classified …check:empty-state✓ all classified (1 closed, 2 open, 4 output, 9 scope)check:variant-docs✓ 19 discriminated union(s) — 8 governed, 11 exemptcheck:exported-any✅ no exported type resolves to any: 1893 types + 1638 schemascheck:skill-examples✅ 202 prose examples type-checkturbo run typecheckTasks: 122 successful, 122 totalturbo run testTasks: 133 successful, 133 total严格性台账:
docs/audits/2026-07-unknown-key-strictness-ledger.md:475的mapping.zod.ts(3 sites,authorable (p))未改 —— 纯改名不动 site 数,已实跑check:strictness-ledger确认,没有假设。已合并
main(4 个新提交)并重验合并后
packages/spec两侧都动过(#4687 退役IDataEngine.batch?),按 AGENTS.md §10 重跑了pnpm install --frozen-lockfile+ specbuild+check:generated,没有走 git 的文本合并结果。新落地的 #4675 合并驱动已由pnpm install注册,node scripts/check-regen-pending.mjsexit 0。对
origin/main的 delta 复核:dual-source 基线恰好少两行、其余 14 行零改动;authorable-surface.json恰好 11 增 11 删。⛔ 未碰
content/docs/releases/;⛔ 未手编packages/spec/authorable-surface.json(全部经gen:schema由承接表重新生成)。Generated by Claude Code