Skip to content

refactor(spec)!: 双源清账 C12 — FieldMapping 三源改名为 ConnectorFieldMapping / ImportFieldMapping (#4703) - #4710

Merged
os-zhuang merged 2 commits into
mainfrom
claude/issue-4703-field-mapping-tri-source
Aug 2, 2026
Merged

refactor(spec)!: 双源清账 C12 — FieldMapping 三源改名为 ConnectorFieldMapping / ImportFieldMapping (#4703)#4710
os-zhuang merged 2 commits into
mainfrom
claude/issue-4703-field-mapping-tri-source

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes #4703

#4535 的 C12 簇。基线 16 → 14,其余 14 行一字未动。

复核结论:三侧确实是两个概念,改名而非收敛

PM 的定位表我对 post-C9 的 origin/main 逐条自验过,完全属实:

声明 形态 authorable key
./shared shared/mapping.zod.ts:102 ,plain z.object 4
./integration integration/connector.zod.ts:105 Base.extend({dataType, required, syncMode}) 7
./data data/mapping.zod.ts:97 独立 strictObject 4

./shared./integration 确实是基与超集,所以「该不该收敛」是个真问题。我的结论是不能,两个方向都不行:

  • 把基加宽到 7 个 key —— automation/sync.zod.tsdata/external-lookup.zod.ts 也 extend 这个基,等于把 connector 的同步语义(syncMode / required / dataType)推给 ETL 同步和外部查找;
  • 把 connector 侧收窄到 4 个 key —— 那是退役三个 live key,不是命名修复,得走 ADR-0049 那一整套。

./data 根本不是同一个概念。三条硬证据我都实测并钉住了:

  1. transform 同名不同类型 —— ./shared+./integration 收判别联合 { type: 'cast', targetType };./dataTransformType 普通枚举 + 平铺 params 袋子,默认 'none'。互相都解析不过
  2. 基数不同 —— ./datasource/targetstring | string[](一个目标字段可由多列 split/join 合成),另两侧只收 string
  3. 未知键失败模式相反 —— ./datastrictObject(未知键静默剥离仍是全仓默认:把 #3405 的 strict 收紧从一个 schema 推广到整个可授权面(ADR-0078 完整性闸门) #4001),未知键 throw 并给出别名/拼写处方;另两侧是 plain z.object,静默 strip。同一个 typo,一边硬报错一边什么都不做。

ADR-0112 D9(a):基保留裸名,两个领域专用侧加领域前缀。这不是新发明 —— 同仓 data/external-lookup.zod.ts:86ExternalFieldMappingSchema extend 的是同一个基,正因为带前缀,它从来没进过基线

integration/FieldMapping -> integration/ConnectorFieldMapping   (7 keys)
data/FieldMapping        -> data/ImportFieldMapping             (4 keys)
shared/FieldMapping      -> 原样不动

11 个 key 逐条核对(4 + 7,一个不少)

authorable-surface.json 总量 8265 → 8265,只有 11 行改名,没有任何增减:

def key 状态
integration/ConnectorFieldMapping source target transform defaultValue dataType required syncMode 7/7 承接
data/ImportFieldMapping source target transform params 4/4 承接
shared/FieldMapping source target transform defaultValue 未进承接表,原样

零 tombstone、零 ADR-0087 conversion —— check:spec-changes 全程绿,没有任何 key 离开契约。

承接表:发现并修了两个只在「多条目」下才成立的洞

本簇是 RENAMED_DEFS(#4684)的第一个真实消费者,也是它第一次同时装两条。装上之后有两条规则才开始有意义,原表都没有:

洞 1 —— 两个 source 指向同一个 target,checkRenameTable 不报错。 这不是两次改名,是一次合并,而且它恰好击穿承接表存在的理由:build-schemas.ts 把快照 carry 进一个按新 key 索引prev map,于是两个 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 / #4689TypeScript compiler API 做符号身份解析,含两道防空转守卫(expect(moduleSym).toBeTruthy()origins.size > 20)。

C9 那条通用不变式 pin 的 KNOWN_STILL_DUAL_SOURCE 已按设计清空为 []

sabotage 实测输出

1. ./integration 重新加回 export type FieldMapping(旧名兼容别名——本簇明令禁止):

× no name resolves to two declarations across ./shared and ./integration (types included)
× no name resolves to two declarations across ./shared, ./integration and ./data (types included)
AssertionError: expected [ 'FieldMapping' ] to deeply equal []
AssertionError: FieldMapping must be gone from ./integration: expected 'src/integration/connector.zod.ts:149' to be undefined
Tests  2 failed | 46 passed (48)

C9 的清单 pin 和新的三源 pin 同时变红 —— 那个强制握手是活的。

2. ./data 重新加回 FieldMapping 类型 + schema 别名:

× each entry exposes exactly one field-mapping name, and not the others’
× no name resolves to two declarations across ./shared, ./integration and ./data (types included)
AssertionError: FieldMapping must be gone from ./data: expected 'src/data/mapping.zod.ts:253' to be undefined

3. 概念差异 pin —— 把 ./datatransform「顺手统一」到共享判别联合:

× `transform` means a discriminated union on two sides and a flat enum on the third
× ./data accepts arrays for source/target where the other two take a single string
AssertionError: expected undefined to be 'none'
"message": "Invalid input: expected object, received string"

4. 把 ./datasource/target 收窄成单个 string:

× ./data accepts arrays for source/target where the other two take a single string
"message": "Invalid input: expected string, received array"

5. 把 ./datastrictObject 改回 z.object:

× an unknown key THROWS on ./data and is silently stripped by the other two
AssertionError: expected true to be false

6. 防空转守卫本身(把 ./data 入口指向不存在的文件):

AssertionError: ./data module symbol must resolve: expected undefined to be truthy
Tests  1 failed | 47 skipped (48)

承接表的 sabotage(对改名前的快照跑,否则表已对新快照惰性)

7a. 两条承接都在 —— 只报「新增未记录」,没有任何 key 丢失,正好 11 条:

❌ authorable-surface.json is out of date (11 key(s) not recorded).
     + data/ImportFieldMapping:params …(4)
     + integration/ConnectorFieldMapping:dataType …(7)

7b. 删掉 data/FieldMapping 那条承接:

❌ 4 authorable key(s) disappeared from the contract:
     - data/FieldMapping:params
     - data/FieldMapping:source
     - data/FieldMapping:target
     - data/FieldMapping:transform

7c. 声明了改名,却把 syncMode 从新 def 里删掉(检查 (a0)):

❌ 1 authorable key(s) were lost by a declared def rename:
     - integration/FieldMapping:syncMode  →  integration/ConnectorFieldMapping:syncMode  (absent)

7d. 旧名留着不删(是拷贝,不是改名):

❌ 1 problem(s) in RENAMED_DEFS (scripts/lib/renamed-defs.ts):
     - data/FieldMapping → data/ImportFieldMapping: the SOURCE def is still emitted by
       this build. That is a copy, not a rename — and a copy is precisely the
       dual-source shape this table must never be able to launder (#4411, #4446).

预期的「非回归」变化:文档页归属(#4696)

gen:docs 删掉了 content/docs/references/integration/mapping.mdx,内容并入 integration/connector.mdx这是修复,不是回归,机制已定位到行:

build-docs.ts:136schemaZodFileMap 是一个跨 category 的全局 map,按裸 schema 名索引。FieldMapping 被写了三次(data/mapping.zod.tsshared/mapping.zod.ts 两个 slug 都是 mapping,integration/connector.zod.ts slug 是 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(FieldMappingImportFieldMapping),它直接点名了被改的导出。:217./shared 那行本来就正确,未动。

changeset:major,但作者写的元数据零迁移

据实定级,没有照抄 C9 或 C11:

  • major —— 两个 entry 的 TS 导出真的改名了,import { FieldMappingSchema } from '@objectstack/spec/data' 会编译失败。
  • 元数据零迁移 —— 11 个 authorable key 名字、类型、默认值、严格性全部不变。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 build exit 0
check: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 test Test Files 293 passed (293) / Tests 7374 passed (7374)
pnpm --filter @objectstack/spec typecheck exit 0
check:strictness-ledger ✓ 67 file(s) across 5 triaged directories — site counts match
check: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 exempt
check:exported-any ✅ no exported type resolves to any: 1893 types + 1638 schemas
check:skill-examples ✅ 202 prose examples type-check
全仓 turbo run typecheck Tasks: 122 successful, 122 total
全仓 turbo run test Tasks: 133 successful, 133 total

严格性台账:docs/audits/2026-07-unknown-key-strictness-ledger.md:475mapping.zod.ts(3 sites,authorable (p))未改 —— 纯改名不动 site 数,已实跑 check:strictness-ledger 确认,没有假设。

已合并 main(4 个新提交)并重验

合并后 packages/spec 两侧都动过(#4687 退役 IDataEngine.batch?),按 AGENTS.md §10 重跑了 pnpm install --frozen-lockfile + spec build + check:generated,没有走 git 的文本合并结果。新落地的 #4675 合并驱动已由 pnpm install 注册,node scripts/check-regen-pending.mjs exit 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

claude added 2 commits August 2, 2026 20:04
…, 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
@vercel

vercel Bot commented Aug 2, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 2, 2026 8:11pm

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation protocol:data tests tooling size/l labels Aug 2, 2026
@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

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

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

  • content/docs/ai/agents.mdx (via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx (via @objectstack/spec)
  • content/docs/ai/skills.mdx (via @objectstack/spec)
  • content/docs/api/client-sdk.mdx (via @objectstack/spec)
  • content/docs/api/environment-routing.mdx (via @objectstack/spec)
  • content/docs/api/error-catalog.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-client.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx (via @objectstack/spec)
  • content/docs/api/index.mdx (via @objectstack/spec)
  • content/docs/automation/approvals.mdx (via @objectstack/spec)
  • content/docs/automation/connectors.mdx (via @objectstack/spec)
  • content/docs/automation/flows.mdx (via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx (via packages/spec)
  • content/docs/automation/hooks.mdx (via @objectstack/spec)
  • content/docs/automation/index.mdx (via @objectstack/spec)
  • content/docs/automation/webhooks.mdx (via @objectstack/spec)
  • content/docs/automation/workflows.mdx (via @objectstack/spec)
  • content/docs/concepts/architecture.mdx (via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx (via packages/spec)
  • content/docs/concepts/index.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx (via packages/spec)
  • content/docs/concepts/north-star.mdx (via packages/spec)
  • content/docs/data-modeling/analytics.mdx (via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx (via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx (via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx (via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx (via @objectstack/spec)
  • content/docs/data-modeling/index.mdx (via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx (via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx (via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx (via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx (via @objectstack/spec)
  • content/docs/deployment/cli.mdx (via @objectstack/spec)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx (via @objectstack/spec)
  • content/docs/getting-started/build-with-claude-code.mdx (via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx (via @objectstack/spec)
  • content/docs/getting-started/examples.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx (via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/spec)
  • content/docs/kernel/cluster.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx (via packages/spec)
  • content/docs/kernel/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx (via packages/spec)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/spec)
  • content/docs/kernel/services.mdx (via @objectstack/spec)
  • content/docs/permissions/authorization.mdx (via @objectstack/spec)
  • content/docs/permissions/permission-sets.mdx (via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx (via @objectstack/spec)
  • content/docs/permissions/positions.mdx (via @objectstack/spec)
  • content/docs/permissions/rls.mdx (via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx (via @objectstack/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx (via @objectstack/spec)
  • content/docs/plugins/development.mdx (via @objectstack/spec)
  • content/docs/plugins/index.mdx (via @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx (via @objectstack/spec)
  • content/docs/protocol/diagram.mdx (via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/runtime-capabilities.mdx (via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/query-syntax.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx (via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx (via @objectstack/spec)
  • content/docs/releases/implementation-status.mdx (via @objectstack/spec)
  • content/docs/releases/index.mdx (via @objectstack/spec)
  • content/docs/releases/v12.mdx (via @objectstack/spec)
  • content/docs/releases/v13.mdx (via @objectstack/spec)
  • content/docs/releases/v16.mdx (via @objectstack/spec)
  • content/docs/releases/v17.mdx (via @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/spec)
  • content/docs/ui/actions.mdx (via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx (via @objectstack/spec)
  • content/docs/ui/dashboards.mdx (via @objectstack/spec)
  • content/docs/ui/forms.mdx (via @objectstack/spec)
  • content/docs/ui/index.mdx (via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx (via @objectstack/spec)
  • content/docs/ui/setup-app.mdx (via @objectstack/spec)
  • content/docs/ui/translations.mdx (via @objectstack/spec)
  • content/docs/ui/views.mdx (via @objectstack/spec)

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 free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation protocol:data size/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

spec 双源清账 C12:FieldMapping / FieldMappingSchema(./data ≠ ./integration ≠ ./shared,三源)—— 2 条

2 participants