fix(spec): wait-timeout 处方改印能真正解析的 timerDuration: '60000'(#6758) - #6847
Conversation
`waitEventConfig.timeoutMs` 墓碑与 `timeout` 拼写错误的 `guidance` 条目都让作者 写 `timerDuration: 60000`,而 `timerDuration` 是 `z.string()`。照做的作者先吃 TS2322,再吃一个不带处方的裸 `invalid_type` —— 正是墓碑要替他们挡掉的两个错误。 ADR-0087 转换早就写对了(`String(next.timeoutMs)`),只有散文说反。 两处处方改印引号形式,并加自校验 pin:从消息里**提取**被规定的值再喂回 schema (spec 侧)和 `parseIsoDuration`(service-automation 侧)。 接受面逐字节不变:只改 `retiredKey()` guidance 参数、`strictObject` guidance 取值与 TSDoc。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018ffcE95NaMJcL9XJ9VDYgk
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckThis PR changes 1 package(s): 112 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 31286857841 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 失败的 job(日志抽取,best effort):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
|
Queue-failure triage — unrelated to this PR. Re-queued once; flaky test filed as #6855. Following the checklist in the triage comment above rather than re-queuing blind. The failure:
Checklist item 1 — is it in a package this PR touched? No. This PR changes a Checklist item 2 — seen elsewhere? Not previously: the triage bot recorded "本 PR 过去 24h 无队列失败记录(首次)", and a repo-wide search for the test name returns only #6730/#6015 contract discussion, no flaky report. So this is the first sighting rather than a proven-flaky re-run. What decides it: PR #6846 merged as The test is also brand new: Action taken: re-queued once (checklist item 3), and filed #6855 so the CLI seat can make the ordering assertion causal rather than temporal — the ordering property itself is what #6531 ruled on and is worth keeping asserted. Filing it matters because a timing-sensitive assertion in the shared full-suite path taxes every PR in the repo, and without a record each seat that hits it re-derives the diagnosis from scratch. ⛔ Not re-queuing again if it fails on the same signature — that would burn a full-queue rebuild per attempt. Next step in that case is escalation, not repetition. Generated by Claude Code |
|
Queue steward — yielding (队列管家让行). The merge-queue build 31286857841 ( Yield basis (SKILL「入队与落地 B」bidirectional yield): reading this PR's last 30 minutes of comments first, the lane PM had already triaged at 01:01:39Z — re-queued once and filed the flaky as #6855. That predates this seat's reading, so the steward yields: no second diagnosis, no re-queue, no further action on this PR. Signature recorded for the round briefing (full log archive pulled, not tail — Operational note 7):
Outcome: this PR merged at 01:16:46Z on its re-queue, so the ejection cost one queue cycle and nothing is outstanding here. One steward-side follow-up, filed on the anchor issue rather than here: this signature is not in any of the four #5810 signature ledgers, so the four-branch triage classifies it as a new signature — which means every future steward round must refuse to re-queue on it and pay a full cycle. The ledger is human-upgrade-only, so the steward has left a registration request on #5810; it is not adding the row itself. Generated by Claude Code |
Fixes #6758
问题
waitEventConfig上有两处面向作者的文案让人写timerDuration: 60000,而timerDuration是z.string():timeoutMs墓碑(retiredKey())—— 升级作者写了已retire的键时看到的处方;timeout拼写错误的strictObjectguidance条目 —— 更糟,因为作者没有"第一次失败"可学,这是 schema 对这个块说的第一句话。照着处方写的作者,先在编写处吃 TS2322,再在解析时吃一个不带任何处方的裸
invalid_type——正是墓碑存在的意义所要挡掉的那两个错误。代码库自己早就知道正确答案。ADR-0087 转换的文档注释直言:
它写的也确实是
String(next.timeoutMs)。只有散文说反了。实测:
timerDuration到底接受什么不是猜的,是跑出来的(
parseIsoDuration,wait-node.ts):'60000'(裸数字字符串)'PT1M'(ISO 8601)60000(裸数字)'60s'这类简写undefined❌ 没有这种格式所以处方值是加引号的
'60000',与转换的输出、以及conversions/registry.ts的 fixture 完全一致。改了什么
两处处方都改印
timerDuration: '60000',并给出等价的'PT1M',且说明为什么要加引号(键是字符串;裸数字字符串按毫秒读)。同一段 TSDoc 顺带订正 issue 点名的两处:retired in 18→17(两处墓碑与转换的toMajor都是 17,#4350 那一类),以及把「parseIsoDurationaccepts a bare number」改成「reads a bare numeric string」——作者遇到的是 schema,不是那个 helper。接受面逐字节不变
改动全部落在
retiredKey()的 guidance 参数、strictObject的guidance取值、TSDoc 注释里。retiredKey()返回的始终是z.never({ error: () => guidance }).optional(),guidance也只为一个已被拒绝的键提供文案。没有碰任何 Zod 类型、键集合或superRefine。验证:自校验的 pin,方向先预测后运行
pin 不比对硬编码副本,而是从活的报错消息里把处方值提取出来再喂回去——硬编码
'60000'会在有人改写文案的那一刻变绿,而那正是最需要检查的时刻。两个 pin 都带反空转保护(把 guidance 条目删掉会红,而不是因为"没找到东西可查"而绿)。packages/spec/src/automation/flow.test.ts—— 两个渠道印出的每一个timerDuration值都必须过 schema。packages/services/service-automation/src/builtin/wait-node.test.ts—— 走完作者真正要过的两道闸:schema,然后parseIsoDuration。并把墓碑的等式主张("timeoutMs: N和timerDuration: X是同一个 wait")两边都从消息里读出来对拍。预测方向:把散文回退到
origin/main、pin 保留 ⇒ 两个 pin 全红。实测一致:修复后两个 pin 全绿。
一个值得记下的子预测,也应验了:红是 schema 那道闸给的,不是 reader。
parseIsoDuration(60000)返回60000,所以单独的 round-trip 断言在这个缺陷上会是绿的——helper 的宽容在这条路径上根本不可达,这也正是当初写错文案的由来。两道闸缺一不可。为什么两个 package 都要有 pin
timerDuration是z.string(),所以 schema 对任何字符串都放行:只有 spec 侧的 pin,文案被改成timerDuration: 'about a minute'依然全绿。而packages/spec不依赖 service-automation,够不到 reader。所以 round-trip 钉在两者都能 import 的那一侧。Generated by Claude Code