Skip to content

The protection block's unknown-key refusal carries zod's bare message — no surface, no declared keys, no rename, on four authorable types #16845

Description

@claude

Found while fixing #16328 (the manifest permissions block's unknown-key refusal). Filed unassigned and unlabelled for triage. Not fixed there — that card owns kernel/manifest.zod.ts only, and this is a different file reached from four other metadata types.

What was measured

Against packages/spec built from origin/main at f89812e4d, through dist/ai/index.mjs and dist/shared/index.mjs:

AgentSchema.safeParse({ name: 'a', protection: { lockk: 'system' } })

  RAW  code=unrecognized_keys path=["protection"] :: Unrecognized key: "lockk"

  FORMATTED (formatZodError):
    ✗ protection: Unrecognized key: "lockk"

lockk is one keystroke from the declared lock. The author is told the key is wrong and nothing else: no surface name, no declared-key list, and no rename suggestion.

Why this is a defect and not a preference

ProtectionSchema (packages/spec/src/shared/protection.zod.ts:63) is a bare z.object({ ... }).strict() with no error map. It is closed — the refusal is loud, nothing is silently stripped — but it never adopted the strictObject helper that the #4001 unknown-key campaign made the standard for authorable shapes, so it emits zod's own default text instead of the campaign's named message.

Every neighbouring authorable block reached from the same schemas names all three parts. Compare, measured in the same run against the manifest:

Unrecognized key(s) on the `engines` block of this package manifest: `protocl`.
Did you mean `protocl` -> `protocol`? ...

Reach

protection is declared on four schemas, three of which are registered metadata types an author writes by hand:

  • ai/agent.zod.ts:374 (agent)
  • ai/tool.zod.ts:204 (tool)
  • ai/skill.zod.ts (skill)
  • identity/position.zod.ts:127

So defineStack({ agents }), the metadata REST doors and the Studio forms all reach this refusal.

Note on the strictness ledger

docs/audits/2026-07-unknown-key-strictness-ledger.md has no row for shared/protection.zod.ts. Its one near-mention (line 501) is about a counting bug and names kernel/metadata-protection.zod.ts — a different file. So this site appears not to have been enumerated by the campaign rather than deliberately accepted; that is worth confirming at triage before treating it as a plain fix.

Suggested direction

Convert ProtectionSchema to strictObject({ surface, history }, { ... }), exactly as #16328 did for PluginPermissionsSchema. That change cannot move the accept set: strictObject is z.object(shape, { error }).strict(), and an error map is consulted only once an issue is already being raised.

⚠️ The population is not established here. This is one site found while working a neighbouring card, not a sweep. Whether other closed authorable shapes still carry zod's bare message needs its own measured count — #14722's triage already ruled that a sweep needs its own card with a measured population, so this card deliberately does not perform one.


Generated by Claude Code

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions