Repository navigation
strictObject 的 history 句夹在「哪个键错了」与「该写什么」之间,在单行 error 显示位上把修法推到 222 字符之后(#5762 实测) #5955
Description
Activity
分诊:
needs-user-decision(公共授权错误契约的版式取舍)+ 改域domain:devx→domain:spec。⚠️ 本单此前处于**「半分诊」态**(有tooling+domain:devx,无 pm-state 标签)⇒ 对队列视图、决策箱、finding 池三者皆不可见。本轮补状态标签并订正域。为什么改域(anchoring rule,⛔ 按落点不按标题词汇)
原
domain:devx大概率是按「lint / CLI 显示位」这个症状面打的。但四条候选路线的落点是:路线 落点包 域 A(history 排到消息末尾,立单人倾向) packages/spec/src/shared/suggestions.zod.ts:321spec B(仅在无 guidance/rename 时才拼 history) 同上 spec C(结构化返回,消费方渲染) spec 生产者 + 各消费方 spec 主导 D(不改生产者,CLI 改多行渲染) packages/cli/src/commands/validate.ts:141cli 四条里三条(含倾向路线 A)主改动面在
packages/spec⇒ 按 anchoring rule 归domain:spec;且**跨座位转移协议的「shared contract surfaces have one owner」**要求:凡触packages/spec一律归domain:spec座位,无论谁需要它。⇒ 改标。旁证:同文件的 #5593(strictUnknownKeyError直调点迁移)已带domain:spec。为什么进决策箱而不是入队
history拼接位置的改动是对外授权错误契约的措辞/版式变更,不是内部实现:- 影响面立单人已量化 —— 全仓 62 个
strictObject站点 / 293 处history:声明,外加跨包钉死完整消息顺序的测试; - 四条路线各有实测记录的代价(消费者侧正则剥离=契约的第二份拷贝、按长度截断=恰好切掉
Did you mean、查生产者拿原文=接缝刻意不进 barrel 且无 schema 身份可匹配); - 立单人自陈路线 A「属独立 PR + 独立拍板」。
⇒ 「作者看到的错误消息里,沿革句排在修法之前还是之后」是产品措辞取舍,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
- 影响面立单人已量化 —— 全仓 62 个
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
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
Claim: PM loop round 2 (
domain:spec-surfaceseat #6298)
Session:session_01JTSZAjgtL3oR6YcpNDhW3T
Branch:claude/issue-5955-history-sentence-to-end
Worktree:objectstack-issue-5955
Domain:domain:spec-surface
File 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 ispm: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
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
- added 4 commits that reference this issue
on Oct 7, 2026
#5762 把
flow-time-relative-descriptor-invalid从 warning 升为 error 时实测出来的,记录防丢。未指派,交分诊定级。 本条是 #5762 第三问(「zod history 句在 error 显示位的噪音」)里未在该 PR 内解决的那一半 —— 裁定允许折叠,但干净的修法不在那个 PR 的文件面内(见下)。测量
strictUnknownKeyError的消息结构是(packages/spec/src/shared/suggestions.zod.ts:320):history是每个 surface 自己声明的一句沿革。以TimeRelativeTriggerSchema为例,它是 222 字符:实测
validateFlowTriggerReadiness转发后的 finding 全长(#5496 那个真实描述符,三个 zod issue):field+ 缺dateField+ 标量offsetDays)offsetDay)schedule)对比第 1 行与第 4 行:history 句是这条 finding 近 1/3 的长度,且它的位置在「
field错了」与「Did you mean field → dateField?」之间。为什么现在才碍事
CLI 把 finding 打成单行(
packages/cli/src/commands/validate.ts:141):在 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在末尾,截断恰好切掉最有用的部分,比不截更糟。strictObjectDeclarations()(shared/strict-object.ts)与directAliasTables()(shared/alias-table-registry.ts)都刻意不进 barrel,是内部审计接缝,且前者无 schema 身份可匹配。strictObject站点 / 293 处history:声明,以及跨包钉死这些文案的测试。属独立 PR + 独立拍板。候选方向(供分诊/拍板)
… keys. Did you mean …? <history>)。最小改动、修法前置,信息一句不丢。代价:所有钉死完整消息顺序的测试要跟着改。guidance/rename 都无话可说时才拼。噪音只在「有明确修法」时消失,但规则变得依赖分支、可解释性下降。packages/cli而非 spec,影响所有 finding。倾向 A —— 它不删任何信息(#5762 裁定的「保留原始信息可达路径」自动满足),只把「对正在修的人有动作价值的部分」排到前面,且是四条里唯一既不新增分支也不新增通道的。
关联:#5762(实测出处)、PR #5952(记录了完整推理)、#4001(history 机制的来源)、#5593(
strictUnknownKeyError直调点迁移 —— 不同关切:机制迁移,非消息版式)。