From 3a5885190bb0a97e6ca8e08b23c8063c9db38483 Mon Sep 17 00:00:00 2001 From: Klappy Date: Fri, 17 Apr 2026 15:22:31 +0000 Subject: [PATCH] fix(mcp): expand challenge mode enum to include writing-lifecycle modes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- workers/src/index.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/workers/src/index.ts b/workers/src/index.ts index a473b1d..1ac23a6 100644 --- a/workers/src/index.ts +++ b/workers/src/index.ts @@ -167,7 +167,11 @@ Use when: ]).describe("Which epistemic action to perform."), input: z.string().describe("Primary input — query, claim, URI, goal, or completion claim depending on action."), context: z.string().optional().describe("Optional supporting context."), - mode: z.enum(["exploration", "planning", "execution"]).optional().describe("Optional epistemic mode hint."), + mode: z.enum([ + "exploration", "planning", "execution", + "voice-dump", "drafting", "peer-review-ready", + "canon-tier-2", "canon-tier-1", "published-essay", + ]).optional().describe("Optional mode hint. Epistemic modes (exploration/planning/execution) or writing-lifecycle modes (voice-dump/drafting/peer-review-ready/canon-tier-2/canon-tier-1/published-essay). Sourced from odd/challenge/stakes-calibration."), canon_url: z.string().optional().describe("Optional GitHub repo URL for canon override."), include_metadata: z.boolean().optional().describe("When true, search/get responses include a metadata object with full parsed frontmatter. Default: false."), section: z.string().optional().describe("For action='get': extract only the named ## section from the document. Returns section content or available sections if not found."), @@ -232,7 +236,11 @@ Use when: action: "challenge", schema: { input: z.string().describe("A claim, assumption, or proposal to challenge."), - mode: z.enum(["exploration", "planning", "execution"]).optional().describe("Optional epistemic mode for proportional challenge."), + mode: z.enum([ + "exploration", "planning", "execution", + "voice-dump", "drafting", "peer-review-ready", + "canon-tier-2", "canon-tier-1", "published-essay", + ]).optional().describe("Mode for proportional challenge. Epistemic (exploration/planning/execution) or writing-lifecycle (voice-dump/drafting/peer-review-ready/canon-tier-2/canon-tier-1/published-essay). voice-dump suppresses all challenge output."), canon_url: z.string().optional().describe("Optional: GitHub repo URL for canon override."), }, annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },