Skip to content

ADR 0024 — Contract-preserving Platform route split

  • Status: ACCEPTED FOR NS3-113
  • Date: 2026-09-06
  • Scope: apps/web/app/api/platform/** only; no public URL or payload change
  • Authority: direct user request to complete the attached route-split prompt

Context

The former apps/web/app/api/platform/[...path]/route.ts contained the session setup, error mapping and a 730-line string/regex dispatcher for unrelated Platform resources. The current callers use stable /api/platform/... URLs and the current allowlist is an explicit security boundary. The change must reduce review coupling without changing method, body, response, status, error code, session, tenant or capability behavior.

Options

  1. Keep the catch-all and only reorder/extract functions. This has the smallest file diff but keeps route ownership implicit and does not use native resource routing.
  2. Move the shared context/error helpers and expose resource-scoped native route files that reuse the existing semantic owners. This removes the catch-all route while keeping public contracts and DB function signatures stable.
  3. Rewrite the Platform API around a new router/command framework. This could centralize dispatch, but adds a new public/structural owner, increases migration surface and is unnecessary for a file-based route split.

Decision

Use option 2. Each resource-scoped route calls the shared request-context and error helpers, then uses the existing @ctc/db-mysql owners and parsers. The route allowlist remains active and is verified against the same URL/method matrix. No internal Route Handler is called from a Server Component. The implementation does not add a package, public export, provider, schema or tenant branch.

The four attached prompts are treated as the direct user approval for this exact structural scope. The separate use client matrix remains an investigation gate; it does not authorize speculative server-data plumbing.

Old branch to native route mapping

The old path branches now enter the following native route files. The dynamic folder name is an internal Next.js detail; the public URL segment and method remain unchanged. Numeric tenant URLs use the [uuid] folder because Next.js rejects sibling dynamic folders named [id] and [uuid].

Old branch condition Native route file
admin/selection app/api/platform/admin/selection/route.ts
admin/impersonation app/api/platform/admin/impersonation/route.ts
admin/tenants app/api/platform/admin/tenants/route.ts
admin/tenants/:id app/api/platform/admin/tenants/[uuid]/route.ts
admin/tenants/:id/configs app/api/platform/admin/tenants/[uuid]/configs/route.ts
admin/tenants/:id/modules app/api/platform/admin/tenants/[uuid]/modules/route.ts
admin/tenants/:id/modules/:key app/api/platform/admin/tenants/[uuid]/modules/[key]/route.ts
admin/tenants/:uuid/onboarding app/api/platform/admin/tenants/[uuid]/onboarding/route.ts
admin/tenants/:uuid/onboarding/role-capabilities app/api/platform/admin/tenants/[uuid]/onboarding/role-capabilities/route.ts
admin/current-tenant/configs app/api/platform/admin/current-tenant/configs/route.ts
admin/current-tenant/modules app/api/platform/admin/current-tenant/modules/route.ts
admin/current-tenant/modules/:key app/api/platform/admin/current-tenant/modules/[key]/route.ts
admin/current-tenant/credentials/GEMINI_KEY app/api/platform/admin/current-tenant/credentials/GEMINI_KEY/route.ts
admin/current-tenant/role-capabilities app/api/platform/admin/current-tenant/role-capabilities/route.ts
admin/users/:clientId/:userId/password app/api/platform/admin/users/[clientId]/[userId]/password/route.ts
auth/context app/api/platform/auth/context/route.ts
companies/options app/api/platform/companies/options/route.ts
company-profile app/api/platform/company-profile/route.ts
global-search app/api/platform/global-search/route.ts
master/:kind app/api/platform/master/[kind]/route.ts
master/:kind/:uuid app/api/platform/master/[kind]/[uuid]/route.ts
master/carriers/export app/api/platform/master/carriers/export/route.ts
master/companies/options app/api/platform/master/companies/options/route.ts
master/users/options app/api/platform/master/users/options/route.ts
master/users/:uuid/impersonation app/api/platform/master/users/[uuid]/impersonation/route.ts
menu/favorites app/api/platform/menu/favorites/route.ts
menu/favorites/:id app/api/platform/menu/favorites/[id]/route.ts
menu/items app/api/platform/menu/items/route.ts
menu/items/:id app/api/platform/menu/items/[id]/route.ts
menu/navigation app/api/platform/menu/navigation/route.ts
menu/roles app/api/platform/menu/roles/route.ts
menu/roles/:id app/api/platform/menu/roles/[id]/route.ts
menu/roles/:id/access app/api/platform/menu/roles/[id]/access/route.ts
menu/roles/:id/default-menu app/api/platform/menu/roles/[id]/default-menu/route.ts
menu/tree app/api/platform/menu/tree/route.ts
module-pages app/api/platform/module-pages/route.ts
operation-events app/api/platform/operation-events/route.ts
operation-events/:uuid/ack app/api/platform/operation-events/[uuid]/ack/route.ts
ping app/api/platform/ping/route.ts
table-lists/users app/api/platform/table-lists/users/route.ts
users app/api/platform/users/route.ts
users/:id app/api/platform/users/[id]/route.ts
users/:id/activation app/api/platform/users/[id]/activation/route.ts
users/inactive app/api/platform/users/inactive/route.ts
users/roles app/api/platform/users/roles/route.ts
account/password app/api/platform/account/password/route.ts
wiki/* existing app/api/platform/wiki/[[...path]]/route.ts remains outside this split

Consequences

Positive:

  • Resource authorization and DB owner imports are reviewable by route family.
  • Existing callers keep their URLs and contracts.
  • Shared session/origin/allowlist setup and error mapping cannot drift between routes.

Negative:

  • More route files must be kept in sync with the allowlist and contract tests.
  • Native route params must preserve the legacy numeric/UUID parsing behavior exactly.
  • The old catch-all mapping table must be retained in the contract test during the move.

Revisit triggers

Revisit only if a route needs a different session/tenant policy, the allowlist becomes redundant, or contract tests show a response/status mismatch. A future schema or identity migration is a separate decision and is outside this ADR.

---

Source: new-system/docs/decisions/0024-platform-route-and-client-boundary-split.md — ctc repository · This page is an English translation of the source document. Identifiers, file paths and code symbols are unchanged.