fix(spec): automation/etl.zod.ts 的九个别名回到 X / XParsed house convention (#4963) - #5514
Conversation
…ention (#4963) 裸名翻转为 `z.input`(作者写的形状),新增九个 `*Parsed` = `z.infer`(parse 之后的 形状),与 `shared/retry-policy.zod.ts` 记下的 house convention 一致。 翻转之前九个别名全是 `z.infer`,而本文件有六个带 `.default()` 的键,外加 `schedule` 是 `CronExpressionInputSchema` transform —— 于是 `const p: ETLPipeline = { … }`(三仓零 parse site,这就是本文件唯一的授权门) 根本编译不过。SYNC_ARCHITECTURE.md 的三段示例就是证据,同 PR 修到可编译并加 compiler-API 测试逐字编译它们。 两个 ETL factory 去掉为满足自身返回类型而写的 `enabled: true` 与 cron 预包装。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018fxLGQdatPbBUvCgiVxg6D
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckThis PR changes 1 package(s): 109 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
范围外发现补记 —— #5515PR body 的「未做的事」里写了 L3
第四条是 cron 字符串被拒,根因和本 issue 同款: 已 file 为 #5515(未打标签,交 PM triage)。本 PR 里把该文档 ```typescript 块总数钉死为 6,就是为了让 L3 那三段进不进门是一个必须显式做的决定。 Generated by Claude Code |
原文写「六个键」却列了五个具名键 + 一整个 retry 块,内部不自洽;改为逐类点名 (五个具名键 + retry 的五个 + stats 的四个),不再给一个含混的总数。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018fxLGQdatPbBUvCgiVxg6D
原注释说三段 L3 块「两段用 ... 省略」,读起来像第三段没问题;实测第三段 (sapConnector)是完整字面量、报四条诊断,其中三条写的是 schema 会拒收的 键名/取值。已 file 为 #5515,注释直接点名,免得下一个读者以为那是已审阅的豁免。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018fxLGQdatPbBUvCgiVxg6D
Fixes #4963
裁定按认领评论取 A,一次做完:裸名翻转为
z.input(作者写的形状),新增九个*Parsed=z.infer(parse 之后的形状),两个 factory 回归设计意图,SYNC_ARCHITECTURE.md两段示例修到可编译。前提复核(先证,再改)
九个别名现状 ——
origin/main上确认九个全是z.infer且零*Parsed对应物,issue 描述属实。三仓零 importer —— 三个仓都先
git fetch,再对各自origin/main用git -C复测(不用cd):origin/mainHEAD0285f7f99packages/spec/src/automation/etl.{zod,test}.ts自身 + 文档/changeset/生成物;etl.test.ts只 import schema,不 import 任何类型别名4ed82507771554418两个 sibling 仓均有权限,无需退回本仓证据。迁移面为空。
为什么这不是纯风格问题(实测,不是目测)
本文件六个键带
.default(),外加schedule是CronExpressionInputSchema(transform,输出是{ dialect, source }信封)。在z.infer下这些全部必填、裸 cron 字符串被拒。把翻转前的etl.zod.ts放回、只留新测试跑一遍,编译器报的就是这些:翻转后全部为空。
改了什么
packages/spec/src/automation/etl.zod.ts)。裸名 =z.input,新增九个*Parsed=z.infer。四个 enum 别名(ETLEndpointType/ETLTransformationType/ETLSyncMode/ETLRunStatus)两者同型,仍然成对 —— 理由写在文件里:约定的价值就是读者不必先知道九个里哪个带默认值才能选注解,而一个 enum 日后加.transform()就会重开这个 issue。ETLPipelineRun是 wire 形状,也照同一条规则走,与automation/里另一个 wire 形状FlowVersionHistory一致。enabled: true(纯粹在复述 schema 自己的默认值,只因z.infer让键必填才写);schedule直接透传,不再typeof s === 'string' ? { dialect: 'cron', source: s } : s预包装 —— 归一化本来就该在 parse 时发生。保留各自的syncMode/writeMode:那是两个 helper 各自决定的东西(incremental+upsertvsfull+append),这组对比正是这对 helper 存在的理由,读者不该为看懂它去查两个默认值。SYNC_ARCHITECTURE.md。三段ETLPipeline示例全部修到可编译并以「作者形状」示范(默认键省略)。issue 说翻转后「只剩source.config必填一个错误」—— 对 "After (L2)" 成立,对 "Before (L2)" 不成立:那段还缺name和destination(两个都是必填、与默认值无关),一并补齐。docs/audits/2026-07-unknown-key-strictness-ledger.md)。etl.zod.ts行的"仍未关闭"尾巴改为已关闭,并记下这批分类留下的教训:「因为导出的类型就是授权门,所以是 authorable」是一个需要编译验证的断言,不是可以假定的事实 —— 批 12 门读对了,没人编译过。gen:api-surface整体重生成,+9 −0,无手改。测试
新增
packages/spec/src/automation/etl-author-shape.test.ts,走 compiler API 而不是类型级 pin —— #4642 已经确认packages/spec里的条件类型 pin 是 no-op(tsconfig.json排除**/*.test.ts,vitest 从不开typecheck),所以@ts-expect-error/expectTypeOf在这个包里没人读。测试自带 anti-vacuity 守卫,含一个必须报错的harness-self-test探针,否则解析失败会表现为「三段示例全绿」。SYNC_ARCHITECTURE.md的三段 ETL 示例逐字编译(含 import 行,@objectstack/spec/automation通过paths映射到 entry barrel),零诊断。块总数钉死为 6,新增块必须显式分类。ETLPipeline下编译通过、在ETLPipelineParsed下必须红 —— 正负共用一份文档,所以红不可能是「字面量因为别的原因坏了」。syncMode/enabled外全部补齐的字面量,在ETLPipelineParsed下必须报 TS2739 并点名这两个键。(合并在上一支里做不到:TypeScript 只报它找到的最深层不匹配就停,嵌套的writeMode/continueOnError会把顶层两个键完全遮掉。)反向验证的方向,是在跑之前先定的,结果分两类,如实报告:
常规红:把翻转前的
etl.zod.ts放回,author-*探针和三段文档示例全红(诊断见上)。这一半按预期成立。红得不是地方:同一次回退里,
parsed-*探针报的是TS2305: Module '"@objectstack/spec/automation"' has no exported member 'ETLPipelineParsed'/TS2724 … Did you mean 'ETLDestinationParsed' → 'ETLDestination'?—— 名字根本不存在,红是红,但和「方向对不对」无关,不构成证据。所以另做了一次真正的 sabotage:实现打上之后,单把ETLPipelineParsed指向z.input,预期这三条 pin 转红且报「本该有诊断却是空的」。实测:这才是 parsed 半边的 pin 是活的的证据。改回后 16/16 绿。
命令与结果(合并
origin/main之后重跑):packages/spec/api-surface.json相对origin/main的 delta:+9 类型,0 移除。范围外发现
automation/里还有两处X/XParsed未兑现:execution.zod.ts的七对别名两边都是z.infer,ScreenFieldConfig缺Parsed#5507(finding):automation/execution.zod.ts的七对X/XParsed两边都是z.infer,是纯同义词 —— 比automation/etl.zod.ts的九个类型别名全部导出 parsed 形状,违反X/XParsedhouse convention(SYNC_ARCHITECTURE.md 的示例因此不可编译) #4963 的原状更容易误导,因为成对的名字看起来就是约定已兑现。ScheduleState上四个默认键 + 一个 cron transform 完整重现了本 issue 的失败面。但迁移面不为空(service-automation/contracts/automation-service.ts在消费),#4963的「零 importer 所以一次做完」论证在那里不成立,需要单独裁定。同一 issue 记了builtin-node-config.zod.ts的ScreenFieldConfig缺Parsed(纯遗漏,一行)。未做的事
SYNC_ARCHITECTURE.md的三段 L3Connector示例不在本 PR 的编译门内 —— 它们属于integration/connector.zod.ts,其中两段用裸...做省略(不是 TypeScript)。测试里把块总数钉死,就是为了让这条豁免不能悄悄扩大。🤖 Generated with Claude Code
https://claude.ai/code/session_018fxLGQdatPbBUvCgiVxg6D
Generated by Claude Code