improvement(api): retire route contracts that no route serves

Merged
IC
2mo ago
+363-188

Summary

The staging integ-failure alarm fired because integ-session-kb-document-lifecycle/create-document-from-inline-content got HTTP 405 instead of 200.

Root cause: #7179 moved tool operations in process and deleted the routes that only existed to serve them, but createKnowledgeDocumentsContract kept declaring POST /api/knowledge/[id]/documents. The surviving GET/PATCH on that path make Next.js answer POST with 405 rather than an honest 404 — which reads as "wrong verb, endpoint is fine" and sends the caller looking in the wrong place.

Nothing in the repo called that endpoint: the KB UI creates documents through the presigned upload flow, and no SDK, desktop, docs, or client fetch references it. The capability is unaffected either way — knowledge_create_document is a shipped tool that reaches the same use case in process. So this retires the declaration rather than restoring the route.

Audit

Checked all 1125 contracts for the same drift. The knowledge one was the only contract whose path resolves to a live route missing its declared method. 259 others declare paths of routes deleted outright — those 404 honestly and are left alone.

Changes

  • Knowledge create-documents — replaced the route contract with plain params/body schemas plus a named response schema, so nothing declares an endpoint we don't serve. Schemas stay in the contracts tree beside the siblings they share (documentDataSchema is used by the v2 contracts; createKnowledgeDocumentsBodySchema already backed the in-process operation).
  • Deleted 4 contracts with no consumer at all — both TTS contracts, docusign, mistral. Their handlers own strictly better schemas: TTS dispatches by toolId across 8 per-provider schemas instead of one .passthrough() superset covering 7 providers, and mistral bounds pages by the OCR request policy. This removes duplication rather than creating it.
  • Kept crowdstrike and windchill. They look identical from the outside but are load-bearing — crowdstrikeQueryBodySchema is parsed by lib/internal/crowdstrike/execute-tool.ts, windchillOperationResponseSchema by tools/windchill/utils.ts, plus derived types across both.
  • New audit check:api-contract-routes, auto-discovered by run-audits. Also documents the two contract consumption modes on ApiRouteContract, with membership derived (--list-in-process) rather than annotated per file.

Why the new audit isn't redundant with check:route-verbs

check-route-verbs scans routes → contracts, so a contract whose route method was deleted is invisible to it. Verified against the exact regression state: check:route-verbs passes clean (359 handlers) while the new check fails with the precise diagnosis.

Not in scope

The repo is split on where in-process schemas live — 54 lib/internal domains declare them inline, 36 import from contracts, 18 are mixed. Converging on the contracts tree is worth doing but is a 54-domain migration, not this PR. Nothing here makes that split worse: the four deletions each removed a dead duplicate, leaving one source of truth.

Testing

  • bunx turbo run type-check — clean
  • bun run lint:check — clean
  • bun run check:audits — 38/38 pass, including the new audit
  • 1019 tests across 82 files in the touched areas (knowledge, table, tool-operations, contracts, crowdstrike, windchill, mistral, tts)
  • Negative-tested the new audit: reproduces the staging failure at lint time, passes once resolved

🤖 Generated with Claude Code