Repository navigation
ETLPipeline.retry is a third retry-policy vocabulary that #4661 的收敛没有覆盖到 #4962
Description
发现于 #4001 批 12(automation/etl.zod.ts 的 strict 收紧),不在该 PR 范围内,按 Prime Directive #10 单独记录。
事实
#4661 把重试策略收敛成一份声明 shared/retry-policy.zod.ts,因为 automation/control-flow.zod.ts 与 system/job.zod.ts 用同一个导出名 RetryPolicy 发布了两个不同的形状(#4411 陷阱)。收敛后的词表是:
maxRetries // 初次尝试之后的重试次数,default 0(#4661 起 opt-in)
backoffMs // 首次重试前的基础延迟
backoffMultiplier // 指数退避倍数
maxRetryDelayMs // 单次退避延迟上限
jitter // 抖动
retryDelayMs // 墓碑(retiredKey)→ backoffMs
ETLPipelineSchema.retry(packages/spec/src/automation/etl.zod.ts)是同一个概念的第三份编码,而 #4661 没有碰它:
retry: z.object({
maxAttempts: z.number().int().min(0).default(3).describe('Max retry attempts'),
backoffMs: z.number().int().min(0).default(60000).describe('Backoff in milliseconds'),
}).optional()三处分歧,都是真实的:
- 次数键拼写不同 ——
maxAttemptsvs 收敛后的maxRetries。语义相同(初次之后的重试数),只有拼写不同。 - 默认值方向相反 —— 这里
default(3),收敛后的策略default(0)。spec 双源清账 C8:RetryPolicy / RetryPolicySchema(./automation ≠ ./system)—— 2 条 #4661 的理由写得很明确:重试会重放上一次尝试已经产生的副作用,所以"不写 = 不重试"必须是默认,隐式重试是最难在测试里抓到、在生产里最贵的失败模式,而 LLM 写的 metadata 恰恰是"没写出来的键"藏身的地方。ETL pipeline 的 retry 目前正好是被 spec 双源清账 C8:RetryPolicy / RetryPolicySchema(./automation ≠ ./system)—— 2 条 #4661 判为错误的那个方向。 - 少三个键 —— 没有
backoffMultiplier/maxRetryDelayMs/jitter,所以 ETL 的退避是平的、无上限的、无抖动的。一条每天凌晨 2 点跑、失败后固定 60s 重试 3 次的仓库管道,正是 thundering-herd 的典型形状。
之所以 #4661 漏掉它:那次收敛是按导出名冲突(#4411 / #4535 C8)驱动的,而 ETL 的 retry 是一个匿名内联 block,没有导出名,所以不在那次的雷达上。这是同一类债务的另一种外观 —— 一个概念三种词表 —— 只是不表现为 dual-source。
为什么现在只是记录,不是顺手改
etl.zod.ts 在本仓库/objectui/cloud 三仓零 importer、零 parse 点(与 #4738 删掉 L1 sync.zod.ts 时测到的形状相同,区别在于 ETL 不是任何东西的第二份声明,是 SYNC_ARCHITECTURE.md 的 L2 唯一一层,sync-retirement.test.ts 还显式 pin 了 ETLPipelineSchema 必须存活)。所以这里没有"运行时行为"可谈,改动纯粹是可授权契约的形状问题 —— 而契约形状是 needs-decision 级别的取舍,不该塞进一个 strictness 批次里顺手做掉。
批 12 做的是把这个分歧变吵而不是变对:retry block 收紧成 strict 之后,maxRetries 走 alias 提示改写成 maxAttempts,retryDelayMs / backoffMultiplier / maxRetryDelayMs / jitter 各自带一条 guidance,明说"这是 shared/retry-policy.zod.ts 上有、ETL 上故意没有的键,不是笔误"。在本 issue 有结论之前,那四条 guidance 就是这份分歧的可见形式。
三个选项
A. 让 ETLPipeline.retry 直接引用 shared/RetryPolicySchema。
- 长期正确性:最好。一个概念一份声明,spec 双源清账 C8:RetryPolicy / RetryPolicySchema(./automation ≠ ./system)—— 2 条 #4661 已经付过这笔收敛的代价并写下了理由;ETL 是唯一没跟上的那个。
shared/retry-policy.zod.ts的 module JSDoc 里"不进shared/index.ts、由各 domain barrel 再导出以保住 def key"的做法现成可用。 - 让 AI 写的 metadata 不易出错:最好。作者在 job / try_catch / ETL 三个地方看到同一套键和同一个默认语义,不再需要记住"哪个面上叫什么"。
- 成本:
maxAttempts→maxRetries是 breaking 的可授权键改名,需要 ADR-0087 D2 conversion + 墓碑 + changeset 迁移文案;默认值 3 → 0 是行为改变,但此处零 parse 点,所以没有已部署的 stack 会变(这一点和 spec 双源清账 C8:RetryPolicy / RetryPolicySchema(./automation ≠ ./system)—— 2 条 #4661 当时必须写retry-policy-convergedconversion 的处境不同,便宜得多)。 - 副作用:
automation/ETLPipeline:retry在authorable-surface.json的展开会变宽(2 键 → 5 键)。
B. 只统一拼写,不引用共享声明(maxAttempts → maxRetries,默认值改 0,保持内联两键)。
- 长期正确性:差。这是"看起来收敛了"的补丁 —— 词表对上了,声明还是三份,下一次给重试策略加键时 ETL 依然会被漏掉,和这次被漏掉的机制一模一样。付了 breaking 改名的代价却没买到"一份声明"这个唯一值钱的东西。
- 让 AI 写的 metadata 不易出错:中等。拼写一致确实有帮助,但
backoffMultiplier/jitter在别处能写、在这里不能写的坑原样保留。
C. 什么都不做,保留批 12 的 guidance。
- 长期正确性:差,但是诚实的差。分歧被写进了拒绝信息里,不再是静默的;债务可见、可检索、可排期。
- 让 AI 写的 metadata 不易出错:中等偏下。作者会拿到明确的改写指示,但只在他已经写错之后;结构上并没有阻止他写错。
- 成本:零。
建议
A,理由在两个轴上一致:它是 #4661 已经论证过的那个方向(一个概念一份声明),而且此处零 parse 点意味着 A 的迁移成本在整个仓库里此刻最低 —— 越晚做越贵,因为一旦真有 ETL 引擎落地,默认值 3 → 0 就从"改一份 schema"变成"改所有已部署管道的行为"。B 花掉了 A 的全部 breaking 预算却留下了 A 要解决的问题,不推荐。
需要维护者裁决的是 A 里的默认值:maxRetries 跟随收敛后的 0(与 #4661 的 opt-in 论证一致),还是为 ETL 保留 3(与现状一致但重新引入"隐式重试")。我的读法是跟随 0 —— #4661 的论证不依赖于是哪个 domain。
Activity
xuyushun441-sys commentedon Aug 3, 2026
分诊(PM):转
needs-user-decision,与 #4964 合并裁决 —— 两单是同一个方向题的两个键(#4964 =flow.errorHandling,本单 =ETLPipeline.retry),都是 #4661 按「同名导出」收敛照不到的匿名内联块。分开裁会漏掉「一次收敛全家」的选项;两单的 dev 各自给了 A(收编 shared/RetryPolicySchema)的两轴论证,且都指出默认值语义(3 vs 0)需要随裁决一并定。裁决落在哪单,另一单引用即可。Generated by Claude Code
feat(spec)!: reject unknown keys on the ETL authoring contracts (obje…
feat(spec)!: 重试策略的最后两种方言收敛到 RetryPolicySchema (objectstack-ai#4964, o…
test(spec): shared test titles state each cited decision in words ins…