Repository navigation
automation/etl.zod.ts 的九个类型别名全部导出 parsed 形状,违反 X / XParsed house convention(SYNC_ARCHITECTURE.md 的示例因此不可编译) #4963
Description
发现于 #4001 批 12(automation/etl.zod.ts 的 strict 收紧),不在该 PR 范围内,按 Prime Directive #10 单独记录。与 strictness 正交:.strict() 既不改 z.input 也不改 z.infer,本条在批 12 前后完全一致。
事实
house convention 是 X = 作者写的东西(z.input),XParsed = parse 之后的东西(z.infer)。shared/retry-policy.zod.ts 的 JSDoc 把这条写得最清楚,而且记录了它自己就是从违反态改过来的:
Note for pre-17
@objectstack/spec/systemconsumers: this used to bez.infer(the post-parse shape, every key present) on that entry only. It is now the input shape on both, matching the houseX/XParsedconvention used by the sibling control-flow configs.
automation/flow.zod.ts / automation/io-node-config.zod.ts / automation/builtin-node-config.zod.ts 都成对导出(Flow + FlowParsed、NotifyConfig + NotifyConfigParsed……)。
automation/etl.zod.ts 九个别名一个都没跟上,全部是 parsed 形状占用了裸名,且没有任何 *Parsed 对应物:
ETLEndpointType, ETLSource, ETLDestination, ETLTransformationType,
ETLTransformation, ETLSyncMode, ETLPipeline, ETLRunStatus, ETLPipelineRun
= z.infer< typeof …Schema >
为什么这不是纯风格问题
因为 ETL 上有四个带 .default() 的键 —— ETLDestination.writeMode、ETLTransformation.continueOnError、ETLPipeline.syncMode、ETLPipeline.enabled、ETLPipeline.retry 的两个子键。在 z.infer 下它们全是必填。于是"用 ETLPipeline 标注一份手写的 pipeline 字面量"这个最自然的用法直接编译不过 —— 而这正是本文件唯一的授权门(三仓零 importer、零 parse 点,作者接触这个契约的方式就是 const x: ETLPipeline = { … })。
仓库里现成的证据:packages/spec/docs/SYNC_ARCHITECTURE.md 的 Migration Guide,"After (L2)"一段:
const pipeline: ETLPipeline = {
name: 'order_analytics_pipeline',
source: { type: 'api', connector: 'orders' },
transformations: [
{ type: 'aggregate', config: { groupBy: ['customer_id'] } }
],
destination: { type: 'database', config: { table: 'analytics_order' } }
};这段不可编译,两个独立原因:source.config 是必填却没写;syncMode / enabled / destination.writeMode / transformations[].continueOnError 在 z.infer 下必填却没写。同一节的"Before (L2)"更短,同样过不了。因为它们在 .md 里,没有任何 gate 读到 —— 这是"declared ≠ enforced"的文档版本:我们向数据工程师展示了一份平台自己拒绝的 L2 pipeline。
ETL.databaseSync / ETL.apiToDatabase 两个 factory 的返回类型标成 ETLPipeline,因此被迫把 syncMode / enabled / writeMode 显式写死 —— 那不是 factory 的设计意图,是被 z.infer 逼出来的。
两个选项
A. 按 house convention 改:裸名 = z.input,新增九个 *Parsed = z.infer。
- 长期正确性:最好。这正是 spec 双源清账 C8:RetryPolicy / RetryPolicySchema(./automation ≠ ./system)—— 2 条 #4661 对
RetryPolicy做过的同一件事,理由已经论证过,不需要重新论证;etl是automation/里最后一个没跟上的文件。 - 让 AI 写的 metadata 不易出错:最好。作者(人或 LLM)拿到的
ETLPipeline从此就是"我该写什么",把带默认值的键正确标成可省略;写完即可编译,不再需要靠试错发现syncMode其实可以不写。 - 成本:breaking(裸名语义变化 + 九个新导出),需要
gen:api-surface+ changeset 迁移文案(ETLPipeline→ETLPipelineParsed给读取 parse 结果的消费者)。此处三仓零 importer,所以实际迁移面为空 —— 和ETLPipeline.retryis a third retry-policy vocabulary that #4661 的收敛没有覆盖到 #4962 一样,这是此刻做最便宜的一类改动。 - 顺带:SYNC_ARCHITECTURE.md 的两段示例在 A 之后只剩"
source.config必填却没写"一个错误,需要在同一个 PR 里补上。
B. 只修文档示例,类型别名不动。
- 长期正确性:差。文档被改成"符合 parsed 形状"意味着示例里要写满
syncMode: 'full', enabled: true, writeMode: 'append', continueOnError: false这些作者本不必写的键 —— 等于把违反 convention 的后果固化成"推荐写法",下一个照抄的作者会以为这些键是必须的。 - 让 AI 写的 metadata 不易出错:差,且是反向的。LLM 抄文档,抄到的会是一份噪音更多、且暗示"默认值必须显式重复"的模板。
- 成本:最低,非 breaking。
建议
A,两轴一致。这是 #4661 已经在同一个仓库、同一个 automation/ 目录下走过一遍并写下理由的路径,etl 只是没被那次收敛扫到(它没有导出名冲突,不在 #4535 的雷达上 —— 与 #4962 同一个漏网机制)。B 用最低成本换来一份把错误示范写进文档的结果,在"让 AI 写的 metadata 不易出错"这一轴上是负收益。
如果维护者希望把 breaking 面压到最小,可以把 A 拆成两步:先加九个 *Parsed 导出(纯增量,非 breaking),下一个 major 再翻转裸名 —— 但鉴于此处零 importer,我认为一次做完更省事。
Activity
feat(spec)!: reject unknown keys on the ETL authoring contracts (obje…
os-zhuang commentedon Aug 5, 2026
认领:spec 车道第 2 批
会话:
session_018fxLGQdatPbBUvCgiVxg6D分支:
claude/issue-4963-etl-parsed-aliasesWorktree:
objectstack-issue-4963域:
domain:spec文件面:
packages/spec/src/automation/etl.zod.ts+packages/spec/docs/SYNC_ARCHITECTURE.md+ 生成物(api-surface 系列)+ changeset。裁定:取 A(裸名翻转为
z.input,新增九个*Parsed),一次做完不拆两步 —— 三仓零 importer 使迁移面为空,#4661 同目录先例已论证,两轴同向;v17 pre 窗口未关,是最便宜的落地时刻。维护者可否决(否决窗口)。SYNC_ARCHITECTURE.md 两段示例同 PR 修到可编译。Generated by Claude Code
os-zhuang commentedon Aug 5, 2026
验收(spec 车道 PM,
session_018fxLGQdatPbBUvCgiVxg6D):ACCEPT → PR #5514(draft;快速门 10/10 绿,重活在共享 runner 排队,终态由本车道落地扫确认后转 ready 入队)。裁定 A 执行完整且有三处超出模板的判断值得记:
expected '' to contain 'writeMode'—— parsed 半边 pin 活性的真证据。issue 描述与实测的偏差(Before (L2) 还缺 name/destination)如实纠正并补齐。三仓零 importer 复测(
git -C+ 先 fetch)合规,api-surface +9/−0 无手改。两张新单已核实并分诊:#5507(execution.zod.ts 七对纯同义词,finding 持有 —— 迁移面不为空,不适用零 importer 论证,择期单独裁定)、#5515(SYNC_ARCHITECTURE 的 L3 sapConnector 示例教了三个 schema 拒收的键名,bug 入队;其中Connector = z.infer孪生项 20 处裸名在 live parse path,派发时需单独定 appetite)。排空模式下两者暂不派。Generated by Claude Code
fix(spec): `automation/etl.zod.ts` 的九个别名回到 `X` / `XParsed` house conv…
docs(spec): fix the L3 `Connector` example in SYNC_ARCHITECTURE.md an…