Skip to content

Harden registry-drift vocab: build from the projected MCP schema, not Zod internals #27

Description

@bdelanghe

Follow-up to #22 (registry-drift shipped in v0.5.0).

The risk

vocabFromRegistry(reg) in prose.mjs builds the drift vocab by reaching into Zod internals:

const shape = verb.input?.shape ?? verb.input?._def?.shape?.() ?? {};
const opts  = field?.options ?? field?._def?.entries ?? field?._def?.values;

…all inside a try { … } catch { /* verbspec version differences */ } that swallows failures.

If a zod/verbspec bump changes those internals, the vocab silently degrades to {help, version}. And because registryDrift flags an unknown --flag as error, a degraded vocab turns valid references (--catalog, --store, STORE=cas) in body/headline copy into false-positive errors — which would break the consumer site's gate (it keys on error). The swallowing catch hides the regression, and registryDrift has no guard for a near-empty vocab (the original spike no-op'd on flags.size <= 2).

Fix

Build the vocab from verbspec's public projection instead of Zod internals:

  • toMcpToolset(registry) / toMcpTool(verb).inputSchema → JSON Schema, where properties keys are the flags and properties[x].enum are the enum values, and the tool name is the verb id. This is the stable surface test.mjs already pins.
  • Add a fail-safe guard: if the built vocab looks empty (only globals), registryDrift returns [] (no findings) rather than false-positiving valid flags.
  • Add a test that builds the vocab from the real registry (not just a hand-built vocab), so a future zod/verbspec bump is caught by CI instead of silently shipping false errors.

Spike

A zero-dep, runnable demo of the schema-based builder is in the linked PR (spikes/vocab-from-schema.mjs).

Acceptance

  • Vocab built from the projected MCP schema (no _def access).
  • Empty/degraded vocab → registryDrift no-ops (never false errors).
  • Test exercises real-registry vocab extraction.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingenhancementNew feature or request

    Type

    No type

    Projects

    Status
    Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions