Skip to content

OPS-T1 route-shape inventory

Status: DRAFT-S11 — awaiting Liam ratification at S11 close. Source: find app/api -name 'route.ts' on commit 20404054 (ast-dataflow-tooling), plus empirical grep/AST sweep run in kh-ast-S11 R-WP-S11-A. Purpose: Ground the wrap-define-route codemod design in the real distribution of route handler shapes across the KH API surface.


find app/api -name 'route.ts' returns 193 files — matching the Wave 0-A figure (§2.1, S10-wave-0-synthesis.md).


Each route file is assigned a primary shape based on the most significant structural feature. Shapes are mutually exclusive by assignment (a file is placed in the first matching bucket in priority order: CRON > MCP > NAKED_NO_AUTH > multi-method variants > single-method variants).

ShapeDescriptionCodemod-relevant?
AUTH_PLAINSingle-method, auth-wrapped, no dynamic params, no request bodyYes — simplest case
PARAM_BODYSingle-method, auth-wrapped, dynamic path segment [id], plus request.json() / parseBody()Yes — needs params context
BODY_VALIDATEDSingle-method, auth-wrapped, no path params, reads request.json() + parseBody()Yes — straightforward body
PARAMSingle-method, auth-wrapped, dynamic path segment only (no body)Yes — GET-only with params
MULTI_PARAM_BODYTwo or more HTTP methods, dynamic path segment, plus bodyPartial — multi-method needs per-method wrapping
MULTI_BODYTwo or more HTTP methods, no path params, plus bodyPartial — multi-method
CRONRoute under app/api/cron/ — uses cron-secret auth, not getAuthorisedClientMANUAL — different auth model
NAKED_NO_AUTHNo getAuthorisedClient / getAuthenticatedClient call (public or system routes)MANUAL — no auth wrapper to preserve
MULTI_PARAMTwo or more HTTP methods, dynamic path segment, no bodyPartial — multi-method
MCPRoute under app/api/mcp/ — uses MCP transport abstractionMANUAL — bespoke transport handler

ShapeCount% of totalMechanisability
AUTH_PLAIN4020.7 %MECHANISABLE
PARAM_BODY4020.7 %MECHANISABLE
BODY_VALIDATED3116.1 %MECHANISABLE
PARAM2613.5 %MECHANISABLE
MULTI_PARAM_BODY199.8 %NEEDS-REVIEW
MULTI_BODY178.8 %NEEDS-REVIEW
CRON94.7 %MANUAL
NAKED_NO_AUTH63.1 %MANUAL
MULTI_PARAM42.1 %NEEDS-REVIEW
MCP10.5 %MANUAL
Total193100 %

Mechanisability summary:

VerdictCount%
MECHANISABLE (codemod handles end-to-end)13771.0 %
NEEDS-REVIEW (codemod wraps; human confirms per-method schema)4020.7 %
MANUAL (codemod skips; generates report entry)168.3 %

Note on Wave 0-C estimate (10–20 % mechanisation): The Wave 0-C feasibility report quoted 10–20 % because it assessed mechanisation of the entire OPS-T1 migration cost — including the ResponseSchema authoring step that remains manual for all 156 non-fetcher-only routes. The 71 % figure above applies only to the wrapper-insertion sub-task (structural rewriting of the handler signature); the schema-authoring work for routes outside the R-WP17 37-interface baseline is unchanged and remains manual. See §5 for the reconciliation.


Single exported method, getAuthorisedClient / getAuthenticatedClient, no dynamic path segment, no request body. Typically read-only GET handlers.

api/insights/route.ts
api/activity/route.ts
api/dashboard/route.ts
api/intelligence/workspaces/[id]/metrics/route.ts
api/review/stats/route.ts

Canonical pattern:

export async function GET(request: NextRequest) {
const auth = await getAuthorisedClient(['admin', 'editor']);
if (!auth.success) return authFailureResponse(auth);
const { supabase } = auth;
// ... query + return NextResponse.json(payload)
}

Some AUTH_PLAIN routes use withRequestContext wrapping (see §4.11):

export const GET = withRequestContext(async (request: NextRequest) => {
const auth = await getAuthorisedClient(['admin', 'editor']);
// ...
});

Single exported method, auth-wrapped, dynamic path segment [id] (or [canonical_name], [slug], etc.), plus request.json() + parseBody(). Typically POST/PATCH/PUT handlers on resource sub-endpoints.

api/entities/[canonical_name]/type/route.ts
api/entities/[canonical_name]/metadata/route.ts
api/items/[id]/classify/route.ts
api/items/[id]/summarise/route.ts
api/bids/[id]/questions/[qId]/route.ts

Next.js 15 Promise<params> style is used in approximately 78 of the 92 parameterised routes (all shapes combined); the older synchronous style persists in a small number of files that have not been migrated.


Single exported method, auth-wrapped, no path params, reads request body. Typically POST endpoints: create, search, embed, export.

api/embed/route.ts
api/search/route.ts
api/extract/route.ts
api/analysis/route.ts
api/digest/generate/route.ts

Single exported method, auth-wrapped, dynamic path segment, no request body. Typically GET handlers for individual resource reads.

api/entities/[canonical_name]/route.ts
api/items/[id]/layers/route.ts
api/items/[id]/history/route.ts
api/bids/[id]/coverage/route.ts
api/review/assignments/[id]/route.ts

Two or more exported HTTP methods in the same file, with dynamic path segment and request body. The most complex shape for the codemod.

api/items/[id]/route.ts (GET + PUT + DELETE)
api/items/[id]/workspaces/route.ts (GET + POST)
api/guides/[slug]/route.ts (GET + PUT + DELETE)
api/intelligence/workspaces/[id]/route.ts (GET + PATCH)
api/intelligence/workspaces/[id]/sources/[sourceId]/route.ts (GET + PUT + DELETE)

Two or more exported HTTP methods, no dynamic path segment, request body present on at least one method.

api/layers/route.ts (GET + POST)
api/quality/route.ts (GET + POST)
api/bids/route.ts (GET + POST)
api/governance/route.ts (GET + POST)
api/tags/route.ts (GET + POST)

Located under app/api/cron/. Use cron-secret validation (checking x-vercel-cron header or CRON_SECRET environment variable) rather than getAuthorisedClient. Use createServiceClient() to bypass user-scoped RLS.

api/cron/process-queue/route.ts
api/cron/intelligence-poll/route.ts
api/cron/review-cadence/route.ts
api/cron/classification-quality/route.ts
api/cron/coverage-alerts/route.ts
api/cron/content-gaps/route.ts
api/cron/freshness-transitions/route.ts
api/cron/intelligence-cleanup/route.ts
api/cron/quality-score/route.ts

No getAuthorisedClient or getAuthenticatedClient call. Includes public system routes and special-purpose handlers.

api/health/route.ts — system health check (in PUBLIC_ROUTES allowlist)
api/feeds/[workspaceId]/rss/route.ts — public RSS feed
api/feeds/[workspaceId]/rss/filtered/route.ts — public RSS feed (filtered)
api/oauth/decision/route.ts — OAuth consent flow
api/admin/taxonomy-sync/callback/route.ts — admin webhook callback
api/plugin/download/route.ts — plugin download (API key auth)

Two or more HTTP methods, dynamic path segment, no request body.

api/items/[id]/images/route.ts (GET + POST)
api/items/[id]/files/route.ts (GET + DELETE)
api/bids/[id]/templates/route.ts (GET + POST)
api/intelligence/profiles/[id]/route.ts (GET + PUT + DELETE — no body on GET)

api/mcp/[transport]/route.ts (GET + POST + DELETE)

Uses WebStandardStreamableHTTPServerTransport directly (per CLAUDE.md gotcha). Three methods handle different transport lifecycle events. Not wrappable with defineRoute() — the shape is protocol-handler, not data-API.


4.11 withRequestContext wrapping (cross-cutting sub-variant)

Section titled “4.11 withRequestContext wrapping (cross-cutting sub-variant)”

7 route files use withRequestContext from @/lib/logger, which changes the export syntax from export async function METHOD() to export const METHOD = withRequestContext(async () => { … }). This sub-variant crosses AUTH_PLAIN, PARAM_BODY, and MULTI_BODY primary shapes. The codemod must detect this pattern and emit a NEEDS-REVIEW entry (the withRequestContext wrapper must be preserved as an outer wrapper around defineRoute).

Representative examples:

api/items/route.ts (BODY_VALIDATED + withRequestContext → POST)
api/items/[id]/classify/route.ts (PARAM_BODY + withRequestContext → POST)
api/items/[id]/summarise/route.ts (PARAM_BODY + withRequestContext → POST)

Wave 0-C (S10-programmatic-migration-feasibility.md §2 W1) estimated ~15–25 % mechanisation for OPS-T1 overall. The 71 % figure in §3 above measures a narrower axis — wrapper-insertion only — and is compatible with the Wave 0-C figure once the schema-authoring component is reintroduced:

ComponentMechanisabilityRoute count
Wrapper insertion (137 MECHANISABLE routes)~100 % codemod handles137
Wrapper insertion (40 NEEDS-REVIEW routes)~50 % (human confirms schema per method)40
Wrapper insertion (16 MANUAL routes)0 % (codemod skips)16
ResponseSchema authoring (37 R-WP17 fetcher-only)Provided by type-drift-baseline.json37
ResponseSchema authoring (156 other routes)Manual (no known schema)156

The Wave 0-C 20 % headline is a weighted average across all components. The codemod can automate roughly 137 × wrapper + 37 × schema injection = 174 semi-complete transformations, but 156 routes still need a human to author the ResponseSchema Zod object before defineRoute() can be called.

No escalation required — the inventory finding is consistent with Wave 0-C, not a contradiction. The 71 % wrapper-mechanisation figure should be surfaced in the PRODUCT.md and TECH.md to set accurate expectations.


6. Codemod mechanisability verdict per shape

Section titled “6. Codemod mechanisability verdict per shape”
ShapeVerdictRationale
AUTH_PLAINMECHANISABLESingle method, known auth pattern, no params. Codemod inserts wrapper, injects z.unknown() placeholder schema or known schema from baseline.
PARAM_BODYMECHANISABLESingle method, known auth pattern. Codemod extracts params destructure, preserves body parsing.
BODY_VALIDATEDMECHANISABLESingle method, no params. Body schema already present in parseBody() call — can be lifted as ResponseSchema hint.
PARAMMECHANISABLESingle method, GET-only. No body. Schema is always the response type — must come from baseline or remain placeholder.
MULTI_PARAM_BODYNEEDS-REVIEWMultiple methods. Codemod wraps each method individually; human confirms per-method ResponseSchema.
MULTI_BODYNEEDS-REVIEWMultiple methods. Same as above.
MULTI_PARAMNEEDS-REVIEWMultiple methods, no body.
CRONMANUALDifferent auth model (createServiceClient, cron-secret). No user context. defineRoute() contract does not apply.
NAKED_NO_AUTHMANUALNo auth wrapper — defineRoute() presupposes an authenticated handler. Public routes stay as-is.
MCPMANUALProtocol handler, not a data API.
withRequestContext sub-variantNEEDS-REVIEWMust preserve outer withRequestContext wrapper. Shape is complex enough to require human confirmation.

  • Route list generated: find app/api -name 'route.ts' on ast-dataflow-tooling at 20404054.
  • Shape classification: Python grep-based script run against the worktree (S11 kh-ast-S11 R-WP-S11-A).
  • withRequestContext count (7 files) verified by grep -rl "withRequestContext" in app/api/.
  • Auth client count (177 files) verified by grep -rl "getAuthorisedClient\|getAuthenticatedClient" in app/api/.
  • Next.js 15 Promise<params> style count (78 files) verified by grep -rl "params: Promise<{" in app/api/.