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¶
- 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.
- 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.
- 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.