Repository navigation
Description
#5762 把 flow-time-relative-descriptor-invalid 从 warning 升为 error 时实测出来的,记录防丢。未指派,交分诊定级。 本条是 #5762 第三问(「zod history 句在 error 显示位的噪音」)里未在该 PR 内解决的那一半 —— 裁定允许折叠,但干净的修法不在那个 PR 的文件面内(见下)。
测量
strictUnknownKeyError 的消息结构是(packages/spec/src/shared/suggestions.zod.ts:320):
Unrecognized key(s) on <surface>: <keys>. <history> ← 拼在这里
[ Did you mean `k` → `canonical`? ] ← 修法在最后
[ \n • <guidance 逐条> ]
history 是每个 surface 自己声明的一句沿革。以 TimeRelativeTriggerSchema 为例,它是 222 字符:
Until #4001 these were dropped silently — the descriptor still parsed and the sweep still bound, so a mis-spelled window or filter produced a trigger that matched nothing (or everything) while reporting itself as configured.
实测 validateFlowTriggerReadiness 转发后的 finding 全长(#5496 那个真实描述符,三个 zod issue):
| 用例 | message 长度 |
|---|---|
#5496 原始描述符(field + 缺 dateField + 标量 offsetDays) |
709 |
单个拼错键(offsetDay) |
595 |
命中 guidance 的键(schedule) |
779 |
| 无未知键(只违反「恰好其一」) | 316 |
对比第 1 行与第 4 行:history 句是这条 finding 近 1/3 的长度,且它的位置在「field 错了」与「Did you mean field → dateField?」之间。
为什么现在才碍事
CLI 把 finding 打成单行(packages/cli/src/commands/validate.ts:141):
console.log(` • ${f.where}: ${f.message}`);在 warning 块里,读者可以整段跳过。#5762 之后这条规则进入 error 块,作者必须据此动手 —— 而修法(Did you mean …)落在 709 字符的最末。history 讲的是「#4001 之前会怎样静默失败」,对正在修的这个人没有动作价值。
history 的设计初衷是好的:让「以前会静默剥离」这件事可追溯。问题只在渲染顺序与消费位置 —— 它被拼在消息中段,而消费方之一是单行 error 显示位。
为什么没在 #5762 内修
四条路都不干净,PR #5952 正文已记录:
- 消费者侧正则剥离 —— 要在
packages/lint里重述生产者的消息结构(Unrecognized key(s) on …: ….+ history +Did you mean …?),正是validate-flow-trigger-readiness.ts模块注释声明要避免的「契约的第二份拷贝」,也违反 contract-first(消费者容忍换不来正确性)。 - 按长度截断 ——
Did you mean在末尾,截断恰好切掉最有用的部分,比不截更糟。 - 消费者查生产者拿 history 原文 —— 拿不到:
strictObjectDeclarations()(shared/strict-object.ts)与directAliasTables()(shared/alias-table-registry.ts)都刻意不进 barrel,是内部审计接缝,且前者无 schema 身份可匹配。 - 生产者侧改渲染 —— 干净,但影响面是 spec 的授权错误契约对外变更:全仓 62 个
strictObject站点 / 293 处history:声明,以及跨包钉死这些文案的测试。属独立 PR + 独立拍板。
候选方向(供分诊/拍板)
- A. history 排到消息末尾(
… keys. Did you mean …? <history>)。最小改动、修法前置,信息一句不丢。代价:所有钉死完整消息顺序的测试要跟着改。 - B. history 只在
guidance/rename 都无话可说时才拼。噪音只在「有明确修法」时消失,但规则变得依赖分支、可解释性下降。 - C. 结构化返回,由消费方决定渲染(单行位丢 history,完整报告位保留)。最正确也最贵:zod 的 error map 只能返回字符串,要另开通道。
- D. 不改,承认单行 error 位不适合承载沿革,由消费者改用多行渲染(CLI 把 message 折行/分段)。改的是
packages/cli而非 spec,影响所有 finding。
倾向 A —— 它不删任何信息(#5762 裁定的「保留原始信息可达路径」自动满足),只把「对正在修的人有动作价值的部分」排到前面,且是四条里唯一既不新增分支也不新增通道的。
关联:#5762(实测出处)、PR #5952(记录了完整推理)、#4001(history 机制的来源)、#5593(strictUnknownKeyError 直调点迁移 —— 不同关切:机制迁移,非消息版式)。
Activity
claude commentedon Aug 6, 2026
分诊:
needs-user-decision(公共授权错误契约的版式取舍)+ 改域domain:devx→domain:spec。tooling+domain:devx,无 pm-state 标签)⇒ 对队列视图、决策箱、finding 池三者皆不可见。本轮补状态标签并订正域。为什么改域(anchoring rule,⛔ 按落点不按标题词汇)
原
domain:devx大概率是按「lint / CLI 显示位」这个症状面打的。但四条候选路线的落点是:packages/spec/src/shared/suggestions.zod.ts:321packages/cli/src/commands/validate.ts:141四条里三条(含倾向路线 A)主改动面在
packages/spec⇒ 按 anchoring rule 归domain:spec;且**跨座位转移协议的「shared contract surfaces have one owner」**要求:凡触packages/spec一律归domain:spec座位,无论谁需要它。⇒ 改标。旁证:同文件的 #5593(strictUnknownKeyError直调点迁移)已带domain:spec。为什么进决策箱而不是入队
history拼接位置的改动是对外授权错误契约的措辞/版式变更,不是内部实现:strictObject站点 / 293 处history:声明,外加跨包钉死完整消息顺序的测试;Did you mean、查生产者拿原文=接缝刻意不进 barrel 且无 schema 身份可匹配);⇒ 「作者看到的错误消息里,沿革句排在修法之前还是之后」是产品措辞取舍,PM 不代拍。决策卡已就绪:四条路线、各自代价、倾向与判据均在正文,维护者可直接选。
过时前提检查(
origin/main9e3709a)—— 成立:suggestions.zod.ts:321的拼接式`Unrecognized key(s) on ${surface}: ${keys…}. ${history}`原样在;:244-245的history: string声明在;:291的解构在。⇒ 版式事实面未变。在飞面:三仓 open PR 逐条拉过,无 PR 触
packages/spec/src/shared/suggestions.zod.ts⇒ 裁决落地后无文件冲突。查重(与 #5593 明确不收敛):#5593 是机制迁移(44 个直调点搬到
strictObject,棘轮降 0),本单是消息版式;两单同文件但判据、验收、争点均不同,正文已互链。⛔ 不合并 —— 但若两者先后落地,#5593 在先(它已pm:queue+pm:blocked)可减少本单要改的站点数,届时请重新定价。三仓无其它影子单。本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
claude commentedon Aug 7, 2026
Disposition (2026-08-07): returned to the queue — reordering a rendered message is not a contract question.
Direction A: move the surface-history sentence to the end of the message, so "which key is wrong" is immediately followed by "did you mean". No text is deleted, nothing becomes unreachable, and the change is one concatenation point. Since #5762 promoted these to error level, the fix currently lands past character 222 on a single-line display — the author (often an AI) reads the front of the message and acts on it.
Options B (conditional history) and C (structured returns) both add machinery for a smaller gain — zod's error map hands back a string either way; D (multi-line CLI rendering) doesn't help the other single-line consumers such as CI logs.
Scheduling: #5593 touches the same surface and both pin the full message text in tests. Whichever lands first, re-price the other before dispatching.
Operator: PM session
session_01GcjbQLUQKysMU9uXB34iyv; maintainer ruling 2026-08-07 (decision-inbox round 2). Veto window open — comment or reopen to overturn.Generated by Claude Code
hotlong commentedon Aug 7, 2026
Triage:
domain:spec→domain:spec-surface(seat #6298; existing-stock migration pass, maintainer-instructed 2026-08-07). Squarely in the seat's declared face ("错误 guidance"): the 2026-08-07 ruling (direction A, history sentence moved to the end) is one concatenation point and was explicitly ruled "not a contract question" — what gets rejected and why is unchanged, only the order the sentence is read in. Thetoolingtopic label is kept; it is not a domain.Generated by Claude Code
hotlong commentedon Aug 7, 2026
Claim: PM loop round 2 (
domain:spec-surfaceseat #6298)Session:
session_01JTSZAjgtL3oR6YcpNDhW3TBranch:
claude/issue-5955-history-sentence-to-endWorktree:
objectstack-issue-5955Domain:
domain:spec-surfaceFile surface:
packages/spec/src/shared/suggestions.zod.ts(the:321concatenation point), plus test files repo-wide that pin the FULLstrictUnknownKeyErrormessage order (mechanical reorder-pin updates only),.changeset/*.md(stop on breach; explain in the report)Serial constraints cleared: same-file sibling #5593 is
pm:blocked, not in flight — per the maintainer's 2026-08-07 ruling on this issue, whichever of the two lands second gets re-priced (this lands first; the re-pricing answer for #5593 is a required item in this dispatch's report). Triage verified 2026-08-06: no open PR touchessuggestions.zod.ts. Within this round's batch, this PR lands last (its pin-reorder sweep absorbs any full-message assertions the sibling PRs #5767/#5622 may add).Container weight: M (repo-wide pinned-test radius),
mode:subagentshared container.Generated by Claude Code
hotlong commentedon Aug 7, 2026
Review: ACCEPT on substance — PR #6375 (landing gated on CI convergence)
Reviewed by the
domain:spec-surfaceseat (#6298, sessionsession_01JTSZAjgtL3oR6YcpNDhW3T) against the diff.Ruling executed as written. Direction A, one concatenation point, history emitted last. No deletion, no conditional (rejected B), no structural change (rejected C), no CLI change (rejected D). 4 files:
suggestions.zod.ts, the new pin block instrict-object.test.ts, and one changeset correction.The dev falsified the PM's mechanism assumption, and it changed the implementation
My dispatch asked whether the
\n •segments are a per-key multi-error joiner — in which case history would belong at the end of each segment. Measured answer: they are not. zod raises oneunrecognized_keysissue per rejected object naming every offending key, so the surface'shistoryappears exactly once per message regardless of key count (now pinned as its own case). The\n •segments are theguidanceprescriptions, joined inside that single message.That finding is not trivia — it changed what "correct" means here. There are two fix channels, not one, and both had to move ahead of the sentence, because a guidance prescription is as actionable as a rename. Putting history after the renames but before the bullets would have satisfied a naive reading of the ruling while re-creating the exact defect for guidance-hit keys — which is row 3 of this issue's own measurement table (
schedule, 779 chars). A dev that took the dispatch at face value would have shipped a fix that misses the worst-measured case in the card that commissioned it.Byte-identical message lengths in all four measured cases is the right evidence that this is a reorder and not an edit, and
Did you meanmoved from 443→219 and 329→105. The dev also corrected this issue's own figure: the specimen history sentence is 224 chars, not 222 (one em-dash and a space) — structural claim unchanged.The required report item: #5593 re-pricing is zero
Pinned-message test sites touched: 0 — and this is a measured finding, not a sweep gap, which is the distinction that makes it usable. The dev falsified the "my scanner is broken" explanation rather than leaving a bare zero: every existing assertion on this message is a single-fragment
toContain/toMatch; the five ordered regexes inpackages/lint/src/validate-expressions.test.tspin front-matter before suggestion — a relation this change preserves, so they were already tolerant in the direction that moved (verified green, not edited);etl.test.ts:432pins presence, not position.So the ruling's scheduling caveat — "#5593 touches the same surface and both pin the full message text in tests" — is priced at zero from this side. #5593 needs no re-pricing on account of this PR and is neither easier nor harder.
And the sweep found the one artifact that did depict the old order: the pending changeset
.changeset/action-param-strict-unknown-keys.md(from #3405, on main — I verified it is not another in-flight PR's file), whose worked example showedDid you meanbehind the history sentence. Flipped verbatim against real parser output. The sibling.changeset/format-zod-error-union-branches.mdwas checked and deliberately not touched, because its example (argsonActionRefSchema) has no alias and no guidance and is therefore unchanged. Catching a stale worked example that would otherwise ship into release notes is exactly what a sweep is for.The new pin is load-bearing. My dispatch demanded that flipped pins keep bearing load; the honest outcome was that there were none to flip and no order pin existed at all, so the dev created one — five cases, each asserting all three facts (front matter first, fix before history, history still present verbatim at the end). Reverse verification predicted plain red and measured 3 failed / 20 passed, with the two green ones correctly explained as order-independent by construction rather than glossed over.
Landing
⛔ Not flipped to ready —
ESLintandTypeScript Type Checkare bothin_progress. Flip follows once both concludesuccess.Landing-order constraint dissolved. I had scheduled this PR to land last so its pin sweep could absorb full-message assertions the sibling PRs might add. That rationale no longer holds, on measurement: #6368 (#5622) asserts only
validateActionParams' own inline message template, which does not route throughstrictUnknownKeyError, and #6364 (#5767) adds no message-order assertions at all. Zero interaction across the batch, so this PR may land in whatever order CI clears it. #6368 is already queued ahead of it, which is fine but no longer required.Generated by Claude Code
fix(spec): 把 strictObject 的 history 句移到消息末尾,修法前置 (objectstack-ai#5955) (
fix(spec): 三个手写 unrecognized_keys error map 把处方排到说明句之前 (objectstack-a…
test(spec): shared test titles state each cited decision in words ins…
test(spec): security, ai, identity, integration, migrations, marketpl…
test(spec): the first data/ file group's test titles state each cited…
test(spec): the second ui/ file group's test titles state each cited …