feat(spec)!: retire waitEventConfig.timeoutMs / .onTimeout — wait never had a timeout (#4158) - #4198
Merged
Merged
Conversation
…t` never had a timeout (#4158) Both keys described a timeout and neither delivered one, so protocol 18 removes the pair rather than leaving a promise the runtime does not keep (PD #10). `onTimeout` had ZERO readers. No path ever inspected it, so neither 'fail' nor 'continue' ever happened — and its `.default('fail')` stamped a decision nothing made onto every wait node. The showcase set `onTimeout: 'continue'`, which did nothing; it is removed here. `timeoutMs` said "maximum wait time before timeout" while its only reader used it as the timer DURATION when `timerDuration` was absent. It did something, just not what it claimed. Together they declared a timeout `wait` does not have: a run resumes when its timer elapses or its signal arrives, never on a deadline. Real timeout semantics are left unimplemented deliberately — they should be built to a requirement, not retrofitted to fit two keys that happened to be declared. `timeoutMs` CONVERTS to `timerDuration` rather than being dropped, because that is what it did — stringified on the way, since the target is `z.string()` while `timeoutMs` was `z.number()` and `parseIsoDuration` reads a bare numeric string as milliseconds. Moving the number unstringified would have produced a block that no longer parses; a test pins that. With `timerDuration` already set it is dropped instead, having been dead metadata the executor's `??` never reached. Both leave the load path, which is the split the registry already draws: a key retired for being RENAMED keeps a load window, because punishing an author for a spelling nobody warned them about is pointless; a key that MISDESCRIBED itself does not, because silently absorbing it lets the author keep believing they configured a timeout. `api.requireAuth`, the tool/app/flow inert keys and RLS `priority` all left it for the same reason. Three existing tests asserted the opposite — that both keys parse, and that `onTimeout` defaults to 'fail'. They were encoding a contract the runtime never honoured, so they are flipped rather than deleted: the accept cases drop the retired keys, and a new case asserts they are REJECTED with a prescription rather than silently stripped. One fixture interaction, caught by the harness itself: the #4045 lift fixture used `waitEventConfig.timeoutMs` for its fourth ledger entry, and the fixture harness replays the whole table — so its `after` described an end state protocol 18 makes unreachable. It lifts `eventType` instead. Verified: spec 274 files / 7144 tests, service-automation 460, all 8 generated artifacts current (`check:generated`), root gates pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QoA8AV99Ss1RRkLLAYmDzq
|
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:
|
Resolves the protocol-18 rationale conflict in `migrations/registry.ts`: main added the `QueryAST.fields` paragraph (#4196) and this branch added the `wait` timeout pair (#4158). Both are kept and `conversionIds` is the union. Ordering is load-bearing. Main's paragraph says "Like the two above it is a request shape", which is only true while `requireAuth` and `validateOnly` are the two that precede it — so it stays third and the incoming paragraph goes fourth, renumbered from "The third of the same kind" to "The fourth". Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Same conflict, one paragraph later: #4286 appended the joins/windowFunctions sweep to the protocol-18 rationale while CI was running here. Both are kept and `conversionIds` stays the union. The incoming paragraph is appended last, and its opener drops the ordinal — "The fourth of the same kind" became "The same kind of retirement covers". Protocol 18 is collecting an active sweep of retirements (#3963, #4052, #4196, #4286, #4158), so a positional self-reference re-conflicts on every one of them; the ordinal-free opener does not, and matches the "The same major retires" idiom already in the block. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
os-zhuang
marked this pull request as ready for review
July 31, 2026 04:57
This was referenced Jul 31, 2026
Open
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.
Closes #4158。按拍板的「撤回」路线做,不是「实现」。
两个键,谎的方式不同
onTimeout'fail'|'continue',「等待超时时的行为」fail和continue都从未发生;而.default('fail')还把一个无人做出的决定盖在了每个 wait 节点上timeoutMstimerDuration缺席时把它当定时时长用。它做了事,只是和名字不符合起来它们声明了一个
wait并不具备的超时:运行只在定时器到点或信号到达时恢复,从不在 deadline 上恢复。showcase 自己写着onTimeout: 'continue',什么也没做 —— 本 PR 一并删掉。真正的超时语义刻意保持未实现。它应该在有人真的要它时按需求来做,而不是为了迁就两个碰巧被声明的键去反向补一个实现。
timeoutMs是搬走而不是删掉因为它确实做了那件事。搬的时候必须 stringify:目标
timerDuration是z.string(),而timeoutMs是z.number(),且parseIsoDuration把裸数字串读作毫秒 —— 所以timeoutMs: 60000与timerDuration: '60000'是同一个等待。直接搬数字会产出一个不再能 parse 的块,这和 #4161 里
eventType必填那个陷阱同类。有测试专门钉住转换结果仍能FlowSchema.parse。timerDuration已存在时则丢弃而非搬移 —— 那种情况下执行器的??根本够不到它,它早就是死元数据。为什么两个都退出加载路径
这是注册表已经在用的划分,我照着走:
object、notify 别名、sharingfull)api.requireAuth、tool/app/flow 的 inert keys、RLSpriority)两个键都属于后者。迁移链会机械地转换存量源;schema 墓碑给出处方。
三个既有测试被翻转,而不是删掉
flow.test.ts里三个测试原本断言相反的事:两个键能 parse、onTimeout默认'fail'。它们编码的是一个运行时从未兑现的契约。所以 accept 用例去掉退役键,另加一个显式断言 —— 拒绝而非静默剥离,并检查错误里能找到
4158(处方可被定位)。那一段的 TSDoc 写明了它们原本断言的是什么、以及为什么翻转本身就是要点。harness 自己抓到的一个交互
#4045 那个 lift 的 fixture 用
waitEventConfig.timeoutMs演示第四条账本项,而 fixture harness 用includeRetired回放整张表 —— 于是它的after描述了一个协议 18 之后不可达的终态。改成提升eventType,用意(一个提升、一个被遮蔽)不变。验证
check:generatedcheck:generated首次实战刚合入的 #4183 立刻兑现了两件事:
api-surface.json+content/docs/references/**)。CI 顺序执行只会先报一个 —— 那正是我在 fix(automation,lint,spec): validate the expression slots a node'sconfigSchemadeclares (#4027) #4040 和 fix(spec,service-automation): the wait executor reads its declared contract only (#4045) #4161 上各多推两轮的原因。api-surface是过期-dist 幻影:它就地打印了「这个闸门读构建产物」的说明,重新构建后自行消失,只剩check:docs是真过期。--fix于是只重生成了那一个。一处既有的文档渲染问题,非本 PR 引入
嵌套的退役键在生成参考里渲染成
timeoutMs?: any,读起来像「什么都能传」,与「已移除」相反(顶层退役键能带上[REMOVED] …说明)。这是既有行为:
validateOnly?: any今天就在content/docs/references/api/batch.mdx里 —— 正是我引为同类先例的 #4052 那个退役键。本 PR 只是又加了两例,未在此处修,记录备查。🤖 Generated with Claude Code
https://claude.ai/code/session_01QoA8AV99Ss1RRkLLAYmDzq
Generated by Claude Code