Skip to content

[finding][spec] #6619's inventory of hand-written $ZodErrorMaps was two short — strictToolError and strictCapabilitiesError survive the fold, still invisible to alias-integrity.test.ts #6805

Description

@os-project-manager

Found during PM review of PR #6804 (#6619's fold), by re-running the census the card rested on rather than taking its count. Filed unassigned for triage.

This is not a defect in PR #6804. That PR folded exactly the three maps its card named, and did so correctly. What is wrong is the inventory — inherited from #6416 and repeated by #6619 — which asserted those three were all of them.

The claim that does not hold

#6619's body states, of the three maps it folds:

#5593 has now landed … measured on the merged ref, all three hand-written $ZodErrorMaps survive it untouched … So they are now the only alias/guidance tables in the package that no registry sees.

PR #6804 repeats it: the fold closes "#6416 命名的盲区,就此闭合".

The blind spot #6416 named is indeed closed. The blind spot class is not.

The measurement

Census of z.core.$ZodErrorMap under packages/spec/src, excluding *.test.ts, run on both refs.

origin/main — six hand-written maps (plus the shared machinery in strict-object.ts and suggestions.zod.ts, which is the template itself, not an instance):

Symbol Site Disposition
strictVisibilityError shared/visibility.ts:113 folded by #6804
strictWidgetAnalyticsError ui/dashboard.zod.ts:163 folded by #6804
strictTenancyError data/object.zod.ts:442 folded by #6804
strictToolError ai/tool.zod.ts:83 survives
strictCapabilitiesError data/object.zod.ts:160 survives
uniqueScopeError data/field.zod.ts:271 survives — different class, see below

On PR #6804's head (47be8449) the first three are gone and the last three remain.

The two that are the same class

Both are unrecognized_keys prescription tables attached to a .strict() object via { error: … } — byte-for-byte the shape the fold was built for.

ai/tool.zod.ts:83, consumed at :180 (}, { error: strictToolError }).strict()):

const strictToolError: z.core.$ZodErrorMap = (issue) => {
  if (issue.code !== 'unrecognized_keys') return undefined;
  const keys = (issue as { keys?: readonly string[] }).keys ?? [];
  const lines = keys.map((key) =>
    TOOL_RETIRED_KEY_GUIDANCE[key] ?? `\`${key}\` is not a ToolSchema field.`,
  );

It carries TOOL_RETIRED_KEY_GUIDANCE — a per-key retirement prescription table, which is precisely the kind of content #6416 wanted under the integrity gate, and it is hand-maintained.

data/object.zod.ts:160, consumed at :274 (}, { error: strictCapabilitiesError }).strict()) — same shape, same invisibility.

The one that is NOT the same class — do not sweep it in

uniqueScopeError (data/field.zod.ts:271) branches on invalid_union, not unrecognized_keys:

const uniqueScopeError: z.core.$ZodErrorMap = (issue) => {
  if (issue.code !== 'invalid_union') return undefined;

It is a value-level error map (a mis-spelled unique scope), not an unknown-key alias/guidance table. strictObject's guidance channel does not address it and folding it would be a category error. Recorded explicitly so the next agent does not spend a round discovering this.

Why it matters

The argument for the fold was never aesthetic — it was that a hand-rolled map registers in neither registry, so its aliases and prescriptions are unmeasured rather than clean. That argument applies unchanged to strictToolError and strictCapabilitiesError. strictToolError is the more valuable of the two, because a retirement prescription table is exactly the surface that rots silently: the tombstone sweep filed as #6756/#6758 found two live prescriptions pointing at keys that no longer exist, and neither of those tables was under a gate either.

The secondary harm is the claim itself. #6619 and PR #6804 both state the package now has no unregistered guidance tables. Anyone auditing "is the #6416 blind spot closed?" reads that and stops — the same failure shape as #6630's "the maxAttempts tombstone is the whole migration channel" sentence, which was false in the same way.

Suggested direction

Non-binding: fold strictToolError and strictCapabilitiesError the way PR #6804 folded the other three, reusing the KeySetGuidance / guidanceSets machinery it introduced (which did not exist when #6416 was written and removes the reason the earlier fold was hard). PR #6804 also added a closure pin to alias-integrity.test.ts that goes red if a folded map is reverted to a hand-written $ZodErrorMap — extending that pin to fail on any new hand-written unrecognized_keys map, rather than only the folded three, would close the class instead of the instances, and is the cheaper half of this card.

Not in scope

  • uniqueScopeError — different error code, different class (above).
  • The shared machinery in strict-object.ts / suggestions.zod.ts — that is the template, not an instance.
  • No acceptance change. Folding these two must leave ToolSchema and the capabilities block accepting and rejecting exactly what they accept today; only the message assembly moves. If a fold cannot preserve that, it is a different defect class.

Provenance

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions