Skip to content

三个手写 unrecognized_keys error map 绕过 strictUnknownKeyError,把说明句夹在「哪个键错了」与处方之间 —— #5955 的修法与 #5593 的迁移都够不到 #6416

Description

@hotlong

Found while implementing #5955 (PR #6375, direction A: move the surface history sentence to the end of strictUnknownKeyError's message so the fix is read first). Unassigned — filed for triage, not claimed. Out of that PR's scope by construction: the ruling scoped it to one concatenation point in packages/spec/src/shared/suggestions.zod.ts, and none of these sites goes through it.

The finding

Three unrecognized_keys error maps are written by hand instead of via strictUnknownKeyError / strictObject, and each reproduces the exact shape #5955 was filed against — a non-actionable explanatory sentence sitting between the offending key and the prescription that fixes it:

site the sentence in the middle the prescription behind it
packages/spec/src/shared/visibility.ts:94-112 (strictVisibilityError) "Before ADR-0089 D3a these were dropped silently, shipping inert metadata; a mis-layered or stale key is now a loud parse error." (~120 chars) "the canonical key is visibleWhen (ADR-0089) — visibleOn … and visibility … are still accepted as deprecated aliases"
packages/spec/src/ui/dashboard.zod.ts:145-190 (strictWidgetAnalyticsError) "Undeclared top-level keys were dropped silently before strict validation, shipping inert metadata; a stale or mis-layered key is now a loud parse error." (~150 chars) three separate branches: the ADR-0021 dataset migration, the objectui-internal component/data quarantine, and the #5022 drill near-key answer
packages/spec/src/data/object.zod.ts:428-438 (tenancy) "The two supported tenancy modes are: database-per-tenant = environment-level deployment (no object config); row-level isolation = tenancy.enabled + tenancy.tenantField." the per-key \n • bullets built just above it

Every one is built as const base = front matter + sentence and then return base + ' ' + prescription, so the prescription is unreachable by a reader who stops at the front of a single-line render — which is the consumption position #5955 measured (os validate's • where: message at packages/cli/src/commands/validate.ts:141, CI logs, and validateFlowTriggerReadiness, which flattens newlines out of schema text on purpose).

Two sibling maps are fine and worth naming so a fixer does not touch them: packages/spec/src/ai/tool.zod.ts:85-93 and packages/spec/src/data/object.zod.ts:163-171 (enable) carry no explanatory sentence at all — front matter, then bullets.

Why neither in-flight change reaches them

So this is not "the same work, later" — it is a set of surfaces that both campaigns' inventories miss. The aliases/guidance integrity gate has the same blind spot for the same reason: alias-integrity.test.ts judges the two registries (strictObjectDeclarations() and directAliasTables()), and a hand-rolled map registers in neither, so its prescriptions are unmeasured rather than clean.

Candidate directions (for triage, not a recommendation to implement)

  1. Reorder in place — three local edits mirroring strictObject 的 history 句夹在「哪个键错了」与「该写什么」之间,在单行 error 显示位上把修法推到 222 字符之后(#5762 实测) #5955, no mechanism change. Cheapest; leaves three hand-maintained copies of the template.
  2. Fold them into strictObject / strictUnknownKeyError — the guidance channel already models exactly what all three do (visibility.ts's alias pointer, dashboard's three branches, tenancy's mode explainer are per-key prescriptions), so this would delete the copies and pull them under the integrity gate at the same time. Larger, and dashboard's branches are keyed by set membership (LEGACY_WIDGET_ANALYTICS_KEYS, QUARANTINED_WIDGET_KEYS) rather than by exact key, which guidance does not express today — that gap is the real decision.
  3. Leave as-is and accept the divergence.

Direction 2 is the contract-first shape and would close the gate blind spot too, but it needs a call on whether guidance grows a set-keyed form; that is a spec-surface decision, not a mechanical one.

Verification notes

Read off origin/main at bbd2d8d3d. Duplicate search run over open issues on the file paths, on strictUnknownKeyError, and on the message-order wording — no hit. Related: #5955 (the ruling and the measurement this generalizes), #5762 (why the position started mattering), #5593 (the migration that does not cover these), #4001 / ADR-0078 (the strictness ratchet), #5013 / #5483 (the two registries the gate reads).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions