Skip to content

fix(mcp): expand challenge mode enum to all 9 modes — unblocks voice-dump suppression#102

Merged
klappy merged 1 commit intomainfrom
fix/challenge-mode-schema-includes-writing-modes
Apr 17, 2026
Merged

fix(mcp): expand challenge mode enum to all 9 modes — unblocks voice-dump suppression#102
klappy merged 1 commit intomainfrom
fix/challenge-mode-schema-includes-writing-modes

Conversation

@klappy
Copy link
Copy Markdown
Owner

@klappy klappy commented Apr 17, 2026

Schema unblock for the voice-dump suppression invariant

Caught by actually testing the production preview after PR #100 merged. The MCP tool's mode parameter Zod schema only accepted exploration, planning, execution — but odd/challenge/stakes-calibration defines 9 modes. The 6 writing-lifecycle modes were unreachable from the public API.

Demonstration

$ curl -sS -X POST https://main-oddkit.klappy.workers.dev/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{
    "name":"oddkit_challenge",
    "arguments":{"input":"...","mode":"voice-dump"}
  }}'

# Returns: MCP error -32602: Invalid arguments — mode must be
# "exploration" | "planning" | "execution"

The voice-dump suppression invariant — the load-bearing feature PR #100's evidence note specifically calls out — was unreachable from the public MCP tool. Schema validation rejected the call before runtime ever saw it.

Fix

Two enum sites in workers/src/index.ts expanded from 3 modes to all 9.

How three layers of verification missed this

  • TypeScript typecheck: passed (the enum is a valid Zod construct)
  • Parser-fidelity test (97/97): passed (tests parsing, not schema)
  • Smoke test (6/6): passed (tests CLI legacy path, not MCP)
  • Cloudflare preview deploy: succeeded (deploy != contract verification)

The CI "Test CF Preview" job failed on cold-start timeout (red herring) and exercised orient/search/validate/preflight — none of them with mode: voice-dump. The bug was reachable only by calling the tool with the load-bearing mode value through the public contract.

Longer-term follow-up (not this PR)

Drop the enum entirely. Let canon be the validator. Runtime already validates against the calibration table at fetchStakesCalibration time. Having the schema also enforce vocabulary is the same Vodka Architecture anti-pattern PR #100 fixed for stop words — domain opinion living in worker source instead of canon. Tracked as a follow-up issue.

Effect on PR #101 (prod promotion)

Hold #101 until this lands. Promoting PR #100 to prod without this fix ships a feature whose load-bearing invariant is unreachable.

Verification

  • npm run typecheck: clean
  • workers/test/governance-parser.test.mjs: 97/97 against main
  • Manual preview curl with mode=voice-dump pending after this deploys
  • Cloudflare auto-deploys preview on push for re-verification

Lesson recorded

Testing the running preview is not optional. Three layers of verification missed this because none exercised the public API contract end-to-end. Adding a preview-contract test (curl-based, exercises every documented mode value) is the next workflow improvement.


Note

Low Risk
Low risk: only broadens Zod validation for the mode argument on oddkit and oddkit_challenge, with no execution-path changes beyond accepting additional enum values.

Overview
Updates the MCP request schemas so the mode parameter accepts all 9 canon-defined modes (adds writing-lifecycle values like voice-dump, drafting, and published-essay) instead of only exploration/planning/execution.

This unblocks clients from calling oddkit and oddkit_challenge with mode=voice-dump (and related lifecycle modes) without failing argument validation, and clarifies the mode semantics in the parameter descriptions.

Reviewed by Cursor Bugbot for commit 3a58851. Bugbot is set up for automated code reviews on this repo. Configure here.

Production preview test caught a real show-stopper: the MCP tool's mode
parameter Zod schema accepted only exploration/planning/execution but
the calibration governance article (odd/challenge/stakes-calibration)
defines 9 modes. The 6 writing-lifecycle modes (voice-dump, drafting,
peer-review-ready, canon-tier-2, canon-tier-1, published-essay) were
unreachable from the public API — schema validation rejected them
before runtime ever saw them.

Net effect of the bug: the voice-dump suppression invariant — the
load-bearing feature of PR #100, named in evidence as load-bearing —
could not be exercised through the public MCP tool. Internal tests
worked because they bypassed the schema. The CI 'Test CF Preview' job
failed on cold-start timeout (red herring); the real failure was
discovered by manual curl against the preview.

Two sites in workers/src/index.ts:
- Line 170: unified oddkit tool
- Line 235: dedicated oddkit_challenge tool

Both expanded to the full 9-mode enum. Description text updated to
explain the two mode families and call out voice-dump suppression.

Longer-term direction (not this commit): drop the enum entirely and
let canon be the validator. The runtime already validates against the
calibration table at fetchStakesCalibration time — having the schema
also enforce vocabulary is the same Vodka anti-pattern shape that
PR #100 fixed for stop words. Tracked as a follow-up.

Verification:
- npm run typecheck: clean
- workers/test/governance-parser.test.mjs: 97/97 against main
- Manual preview curl with mode=voice-dump pending after this deploys

Lesson: testing the running preview is not optional. PR #100 had
typecheck pass, parser test 97/97, smoke 6/6, AND production preview
deploy succeed — and still shipped a feature the schema blocked from
being called. Three layers of verification missed it because none of
them exercised the public API contract.
@cloudflare-workers-and-pages
Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
oddkit 3a58851 Commit Preview URL

Branch Preview URL
Apr 17 2026, 03:22 PM

@klappy klappy merged commit aa6208d into main Apr 17, 2026
5 checks passed
klappy added a commit that referenced this pull request Apr 17, 2026
Parallels PR #102 which fixed the same enum in workers/src/index.ts.
The local tool registry at src/core/tool-registry.js drives the local
MCP server (src/mcp/server.js) and CLI (src/cli.js) and had the same
3-mode enum, creating a contract divergence between the deployment
surfaces for the same logical API.

Two sites in src/core/tool-registry.js:
- Line 47-55 (unified oddkit tool)
- Line 113-121 (dedicated oddkit_challenge tool)

Both expanded to the full 9-mode enum with description text matching
PR #102's style. Mode families explained, voice-dump suppression called
out in the dedicated tool description.

Sweep verified: grep across src/ and workers/ for the 3-mode string
returns only the first line of each 9-mode block now — zero stale
enums.

Caught by bugbot on PR #103 (the promotion PR for #102). Flagged
appropriately as Low severity (the CF Worker surface is the primary
production path; the local surface is used for dev/CLI). Fixing in a
separate PR so #103 can promote cleanly without rebase.

Verification:
- npm run typecheck: clean
- tests/smoke.sh: 6/6 pass

Longer-term (same as #102 flagged): drop enum entirely and let canon
be the validator. Hardcoding mode vocabulary in schema is the same
Vodka anti-pattern shape challenge stop words used to be.
klappy added a commit that referenced this pull request Apr 17, 2026
)

Parallels PR #102 which fixed the same enum in workers/src/index.ts. The local tool registry at src/core/tool-registry.js drives the local MCP server and CLI and had the same 3-mode enum, creating contract divergence between surfaces.

Both sites expanded to 9-mode enum with description text matching PR #102 style.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant