feat(spec)!: 重试策略的最后两种方言收敛到 RetryPolicySchema (#4964, #4962) - #5162
Merged
Conversation
…ryPolicySchema (#4964, #4962) #4661 converged the retry policy onto one declaration, driven by the dual-source instrument — which asks "how many declarations share one exported NAME?". Two further encodings of the identical concept were invisible to it by construction, both anonymous inline `z.object`s with no exported name: `Flow.errorHandling` (#4964) and `ETLPipeline.retry` (#4962). After a convergence lands, a surviving dialect reads as reviewed-and-kept. Both now build from `retryPolicyShape()` — one declaration of the key set, bounds and defaults across all four surfaces. flow.errorHandling: base delay `retryDelayMs` → `backoffMs`; every other key, bound and default already matched, which is why it looked reviewed. The `retry-policy-converged` D2 conversion gains a flow-level branch. Note the divergence's real cost: `shared/retry-policy.zod.ts` tombstoned `retryDelayMs` and prescribed `backoffMs`, and this block then REJECTED `backoffMs` — reading the newer file was punished. ETLPipeline.retry: `maxAttempts` → `maxRetries` (same number — do NOT subtract one, that belongs to connector `RetryConfig.maxAttempts` which includes the first attempt); default 3 → 0; gains backoffMultiplier/maxRetryDelayMs/jitter. Default 0 on business grounds: an ETL destination is a foreign system by definition, so an implicit retry against a non-idempotent one is a duplicate write. No D2 branch, deliberately — an ETL pipeline is not a defineStack collection and etl.zod.ts has no parse site in any of the three repos, so a walker would advertise coverage that does not exist. The tombstone reaches the only doors there are. Both sites stay strict; 批 11/批 12 curation preserved and updated — the five ETL entries that described the divergence dissolved with it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9
…y convergence - spec-changes.json / protocol-upgrade-guide.md / content/docs/references / skill refs: regenerated via check:generated --fix (only the 4 proved stale). - liveness/flow.json: errorHandling.retryDelayMs → backoffMs (live), plus the `dead` tombstone row the retiredKey requires — it stays in the walked shape (rls.priority precedent). Note `dead` here means RETIRED, not never-read: the key was live in retryExecution right up to the rename. - strictness ledger: flow/etl row prose records the convergence. Strip counts UNCHANGED (1 and 3) — both sit at deliberate wire floors and this is a vocabulary change inside already-closed sites, not a new closure. - docs/automation/flows.mdx: errorHandling now documents backoffMs, plus the breaking-change callout. Also fixes two try_catch `retry` examples still spelling `retryDelayMs` (README + flows.mdx) — #4661 renamed the key but missed these two, and leaving them would teach a tombstoned spelling from the PR that widens the tombstone. - changeset: major, FROM→TO for both spellings and the default change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9
…ry-vocab-converge
…ifacts os-regen deferred spec-changes.json + protocol-upgrade-guide.md (both are object arrays, where git's textual merge is not trustworthy). Rebuilt spec and regenerated; verified BOTH sides survive — #5011's dashboard-widget compareTo rows and this branch's retry-policy rows are all present, and the branch delta vs main is exactly this PR's two entries. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 2 package(s): 107 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
xuyushun441-sys
marked this pull request as ready for review
August 4, 2026 07:31
This was referenced Aug 4, 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 #4964
Fixes #4962
按 2026-08-04 维护者裁决执行:A + 默认 0。方向不重议,本 PR 只落地。
为什么 #4661 漏掉了这两处
#4661 收敛重试策略,驱动它的是 dual-source 仪器,而那个仪器问的问题是「有几个声明共用同一个导出名」。另外两份同概念编码对它是构造性不可见的 —— 两者都是嵌在更大 schema 里的匿名内联
z.object,根本没有导出名可供冲突:job.retryPolicy/try_catch.retryflow.errorHandling(#4964)retryDelayMsETLPipeline.retry(#4962)maxAttempts、默认 3、缺三个键仪器没坏,它精确回答了自己被问的那个问题;那个问题只是不等于大家从它身上读出的那个。而收敛完成之后,残存的方言读起来就像「被审视过并保留」。
这份分歧的代价,恰好落在做对了事的作者身上:
shared/retry-policy.zod.ts把retryDelayMs立了墓碑、叫作者改写backoffMs,而flow.errorHandling随即拒绝backoffMs并索要retryDelayMs—— 读了较新的文件反而被惩罚。AI 作者先读到哪个文件是随机的,所以这是它最容易复现的路径。改了什么
四个面现在全部从同一个
retryPolicyShape()构建。flow.errorHandling:只付retryDelayMs→backoffMs一个词。其余每个键、每个界、每个默认值本来就已经相同 —— 这正是它能存活一个大版本的原因:它看起来像被审过。strategy留在外层(它决定策略是否运行,不是策略的一部分)。ETLPipeline.retry:maxAttempts→maxRetries(数字不变 —— 两者都数「初次之后」的重试;⛔ 不要减一,减一属于integration/connector.zod.ts里同名的RetryConfig.maxAttempts,那个键含首次尝试,是另一个数)、默认 3 → 0、新增backoffMultiplier/maxRetryDelayMs/jitter。默认值为什么定 0
不是「循 #4661 的先例」,是业务依据:ETL 的目的地按定义就是外部系统 —— 数仓、别人的 API、别人的库。对非幂等目的地的静默重试 = 重复写入:第二张发票、第二次导出、第二个 webhook。默认 0 让「重试」成为作者显式声明、并因此自证幂等的动作。而没写出来的键,恰恰是 LLM 写的 metadata 藏东西的地方。
迁移面
flow.errorHandling是活的:retryExecution读这个键(现已改读backoffMs),retry-policy-convergedD2 转换新增 flow 级分支覆盖存量,已部署行为不变。ETLPipeline.retry的迁移面今天是空的,这正是「现在做」的理由:etl.zod.ts在三仓零 parse 站点(批 12 的测量),且 ETL pipeline 不是defineStackcollection,没有任何存量文档可供 walker 触达。所以它刻意不给 D2 步骤,只给墓碑 —— 写一个走不到任何东西的分支,就是在宣告一份并不存在的迁移覆盖度,而那正是本注册表该阻止、不该自己犯的 ADR-0049 错误。一旦 ETL 引擎落地,这个默认值翻转就从「改一份 schema」变成「改所有已部署管道的行为」。验证
retryDelayMs→ 红「key set」+「tombstones」;摘掉 D2 flow 分支 → 红转换 fixture;删掉maxAttempts墓碑 → 红墓碑两条。type,探针在量错东西 —— 修正后才取信)。check:*全绿;pnpm test(spec 7757 + service-automation 665)、全仓typecheck(124 包)、三个示例 appvalidate全 0 退出。main(12 个提交,含 dashboard widgetcompareTo:三个声明分支在 ADR-0021 dataset 路径上全部无效(两个静默丢弃,一个抛错) #5011 / 批 17 / 批 18)后全部重跑,并核对注册表两侧都在。台账
flow/etl两行只动叙述,strip 计数保持 1 和 3 不变 —— 两者都停在刻意的 wire floor,本 PR 是已关闭站点内部的词表变更,不是新的关闭。livenessflow.json里retryDelayMs按墓碑规则保留dead行(retiredKey 让键留在被遍历的 shape 里),并注明此处dead意为已退休而非从未被读 —— 它直到改名前一刻都是 live 的。顺带修掉的两处 #4661 遗留
service-automation/README.md与content/docs/automation/flows.mdx里两个try_catch的retry示例仍写着retryDelayMs。这是 #4661 改名时漏掉的文档,同一个键、同一次收敛;由「加宽该墓碑」的这个 PR 留着一份教人写墓碑拼法的文档会自相矛盾,故一并改正,未另开 issue。🤖 Generated with Claude Code
https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9
Generated by Claude Code