/** * **路由声明表**(S-146,[ref] 波 2 第二刀)—— 「这是什么路由」这件事在本仓的**唯一属主**。 * * ## 病(S-146 债行原文的读数) * 同一件事此前有五份写法、两道扫源码文本的对账门,而那两道门已经**反向塑形了源码**:表里必须写 * 字面量不许引常量(否则门的正则抓不到)、注释里不许出现某两个字符(否则门的非贪婪捕获提前收尾)。 * ① 分派序 = `handle()` 里 37 只 `if (await handleX(...)) return;`,鉴权/排空/破坏性写三道门按**行号** * 插在链中间(4 只 handler 落在凭据门前); * ② `ROUTE_DOMAINS` = 同一条链的域名册,靠一道从 `handle()` 源码文本抽序的门与 ① 对拍(表曾漏员又乱序); * ③ `ROUTE_LABEL_PATTERNS` / `ROUTE_LABEL_LITERALS` = metrics/日志的标签名册; * ④ `isBillableSubmitPath` / `isMethodDispatchedSubmitPath` = 三道 503 计费门的 url 名单; * ⑤ `isCredentialGatedRewrite` = 无凭据部署上的改写门 url×方法 名单。 * 五份各自漂过:wake 不在 ④ 里(三道门对一条烧模型的腿全不生效)、`handleDiagnostics` 从不在 ② 里、 * `/v1/fleet/stream` 整条对 ③ 隐形。 * * ## 形(规则少了几条) * **一行 = 一条路由事实**,两张表: * · `ROUTES` —— **行序 = 分派序**,`position` 说它在全局服务凭据门的哪一侧。门不再「按行号插在链中间」, * 而是在 `pre` 段与 `post` 段之间**恒定一处**执行 ⇒ ① 与 ② 合一(名册就是分派序本身,两者再不可能漂), * 那道「从 handle() 源码抽序对拍」的门随之删掉 —— 不是放松,是它守的事变成了结构上不可违反。 * 🔴 S-146 第三刀 车②:**matcher 落地**。一行多了 `decls` 一列(本域真应答的口集合),而 * `ROUTE_DECLARATIONS` 就是这一列按行序摊平 ⇒「分派表」与「声明表」从此是同一份数据的两个投影, * 不再是两份要对拍的名单;域 handler 不再看 url / method(见 route-ctx.ts `RouteHandler` 头注)。 * · `ROUTE_DECLARATIONS` —— ③④⑤ 合成一张;一行声明它的 `id`(全表唯一身份)、匹配式、它**真被响应** * 的 `methods`、以及它**真有**的性质(`billable` / `credentialGated` / `methodDispatched`), * 没有该性质就不写那一列(零默认值)。 * 🔴 S-146 第三刀 车①:这 115 行的**行本体搬回各域**(`routes/x.ts` 的 `X_ROUTES`)—— 路径的 * 单一属主与 `*_PATH` 常量今天的属主对齐,本文件只做拼接与查找结构。`RouteDecl` / `RouteMethod` * 两个类型住在 `route-ctx.js`(域模块本来就 import 的那只接缝模块),理由见那里的行注。 * 车②:行分两族 —— **可分派行**(`path` / `pattern`,带 `id` + `methods`)与**纯门行** * (`prefix`,facts-only,型上就没有 `id`/`methods`),于是「门行会不会被当成路由应答」这个问题 * 在类型上就不存在。 * * 判据两条,逐字: * · **标签取首命中**(优先序):精确行先于形状行([[ref]] 的结论,这里是结构性的而不是靠行序), * 形状行按声明序;没有任何行给标签 ⇒ `"other"`。 * · **门取并集**(宁紧):一条 url 只要**有一行**声明了某性质,它就有该性质 —— 这正是旧谓词那几个 * `||` 的逐字同义,也是「前缀行比标签行宽」这件事的正解(`/v1/devices/` 下将来长出的任何写动词 * 天然在改写门内,不必等谁想起来补一行标签)。 * * 新增一条路由 = 新建 `routes/x.ts` + **该域自己的 `X_ROUTES` 加一行** + 本表的拼接列表加一段 * (+ `test/route-order-freeze.test.ts` 的冻结表加一行,那是 position 的外部对拍源)。 * `position` / `id` / `methods` / 匹配式都是必填 —— 忘了就是**编译红**,不是静默 404。 * * 🔴 **旧禁令已解除**:标签表此前必须「写字面量不引常量」、注释里不许出现某两个字符 —— 那是两道 * 扫源码文本的门反向塑形源码的产物(它们的捕获正则抓不到常量、非贪婪捕获会被那对字符提前截断)。 * 两道门都改成读本表之后,这条禁令没有了对象,随它的 11 处行注一并删;要引常量就引(`routes/*.ts` * 的 `*_PATH` 常量本来就是那些路径的单一属主)。 * * 🔴 本文件是**叶模块**:只 import `routes/*.ts` 的 handler 与 `X_ROUTES`、以及类型。组合根与流水线 * (`server.ts` / `dispatch.ts`)一律反过来 import 它,值边单向,`test/module-cycle-gate.test.ts` 看守。 * 反向的**类型**边也没有:`RouteDecl` 住在 `route-ctx.js`,域模块 import 的是它,不是本文件。 */ import type { FlatServiceDeps } from "./server.js"; import type { IncomingMessage, ServerResponse } from "node:http"; import type { RouteDecl, RouteDispatchDecl, RouteMatch, RouteCtx, RouteMethod } from "./route-ctx.js"; /** 一只域 handler 相对**全局服务凭据门**的位置。门前 = 未鉴权可达(每一只都要单独论证),门后 = 先过凭据。 */ export type RoutePosition = "pre-credential" | "post-credential"; export interface RouteRow { /** 域入口(`routes/*.ts` 的 `handleX`)。签名一致性是编译期钉:入口漂了形 = 编译红,不是运行时静默 404。 * * 🔴 **方法式**声明(不是 `handler: RouteHandler`):方法式的参数位是**双变**的,于是一只只认自己 * 那几个 `id` 的域入口(`RouteHandler<"a" | "b">`)能落进这张宽表,而域内那只闭集 `switch` 仍按它 * 自己的窄 union 判穷尽。窄↔宽的**绑定**由 {@link row} 工厂在编译期钉死:`decls` 与 `handler` 配错 * (把 B 域的表配给 A 域的入口)就是红,不是运行期的静默全不命中。 */ handler(req: IncomingMessage, res: ServerResponse, match: RouteMatch, ctx: RouteCtx): Promise; /** 本域**真应答的那些口**(S-146 第三刀 车②)—— 一行 ROUTES = 一只域入口 = 它的口集合。 * `ROUTE_DECLARATIONS` 就是这一列按行序摊平,所以「声明表」与「分派表」再不可能是两份名单: * 一只域忘了挂自己的声明 = 它一个口都分派不到,当场全红,而不是静默漂一行。 */ readonly decls: readonly RouteDecl[]; /** 必填 —— 新增路由必须**显式**决定它在凭据门的哪一侧。 */ readonly position: RoutePosition; /** 域名(kebab;= `routes/` 下的模块名)。评审把本表当「域全集」读时它是那份清单的名字列, * `test/route-shape-roster.test.ts` 层E 的能力位词干也取自这里 —— 域的身份是声明出来的, * 不是从函数名反推的。函数真名与它的一致性由 `route-order-freeze` 自检格钉住。 */ readonly label: string; /** **挂载条件**(只有真有条件的行才写):缺席 = 恒挂载。域头的合取式就是挂载条件 —— 阀门没开 ⇒ * 请求落到分派器尾的全局 404(**诚实缺席,不是 501**;判据全文在各域模块头注)。 */ readonly enabled?: (deps: FlatServiceDeps) => boolean; } export declare const ROUTES: readonly RouteRow[]; /** 两段(`dispatch.ts` 的分派循环各跑一段)。段内序 = 本表行序; * 「门前行不许出现在门后行之后」由 `test/route-order-freeze.test.ts` 钉住。 */ export declare const PRE_CREDENTIAL_ROUTES: readonly RouteRow[]; export declare const POST_CREDENTIAL_ROUTES: readonly RouteRow[]; /** * 全表 = 流水线四行 + **每一行 `ROUTES` 自己的 `decls`**,按行序摊平。 * * 🔴 车② 之前这里是一张手写的 `...X_ROUTES` 拼接列表,而它与 `ROUTES` 是**两份**按同一条口径排的 * 名单(「域首次出现的位置」),漏一段 / 排错序都只在标签面才看得见。现在拼接就是 `ROUTES` 的投影: * `a2a-serve.ts` / `workflows.ts` / `runs.ts` 三只文件各占两行域入口,车② 把它们的 `X_ROUTES` * **按行拆成两只数组**各归各行(`A2A_SERVE_ROUTES`/`A2A_RPC_ROUTES`、`WORKFLOWS_ROUTES`/ * `WORKFLOW_AGENT_STEER_ROUTES`、`RUNS_ROUTES`/`RUN_VERBS_ROUTES`)—— 于是「一行域入口 = 一组口」 * 这句话没有例外,拼接列表这件手抄活儿随之消失。 * * 🔴 顺序的判据只有一条:**形状行取首命中**(精确行由 `LABEL_BY_PATH` 先查,前缀行不铸标签 ⇒ 两者 * 与序无关)。按域重排后形状行的相对序确实变了(例如 `SESSION_WAKE_RE` 从 `$verb` 行之前挪到了 * 之后),但每一对「重排后跨了身位」的形状行都**互不重叠**或**同标签**,所以 `routeLabel` 的 * 像逐字未变 —— 这不是靠读出来的,是 `test/route-match-parity.test.ts` 的 `labels` 金样逐格钉住的。 * * 🔴 由 `test/route-shape-roster.test.ts` 层A 看守:本表每一行的样本经 `routeLabel` 必须落非 "other", * 且标签静态段与样本逐段吻合、族段不是占位符。新路由不补本表 ⇒ 那门当场红并点名。 */ export declare const ROUTE_DECLARATIONS: readonly RouteDecl[]; /** * **匹配一次**(S-146 第三刀 车②)—— 一条可分派行对一条 url:命中 ⇒ 具名捕获组表,不命中 ⇒ `null`。 * * 🔴 `null` 与 `{}` 是**两件事**(`{}` = 命中且无参),所以返回值不是 `Record | undefined`。 * 没参与匹配的可选组不入表(`m.groups` 里它们是 `undefined`,原样留着 —— 域内判「这一段在不在」 * 读的就是这个 `undefined`,与改前 `m[i] === undefined` 逐字同形)。 */ export declare function matchDecl(d: RouteDispatchDecl, url: string): Readonly> | null; /** Stable, low-cardinality route label for metrics/logs (ids collapsed to `:id`). */ export declare function routeLabel(_method: string, url: string): string; /** POST endpoints that trigger BILLABLE work — the fail-closed auth guard must cover ALL of them. */ export declare function isBillableSubmitPath(url: string): boolean; /** [ref]([ref] 件5)—— **改写门**:不烧模型、但在没有任何 service credential 的部署形下必须 * 与 billable / bake 两族一样 fail-closed 的写门。与 `isBillableSubmitPath` **刻意分开**:这几扇门 * `billable=false`(零模型工作),塞进那张名单会让它们连带吃 drain 与 roster-pending 两道 503 —— * 而收编与规则读写在排空期/roster 未落时做完全无害。 */ export declare function isCredentialGatedRewrite(method: string, url: string): boolean; /** 见 `RouteDeclFacts.methodDispatched` 的头注(单 URL 多方法端点)。 */ export declare function isMethodDispatchedSubmitPath(url: string): boolean; /** 一次分派查找的结果:命中的行与它的参数,外加**这条路径上所有行**的方法并集(405 的 `Allow`)。 */ export interface RouteLookup { /** 方法也对上了 ⇒ 这一行;路径对上了但方法都不在 ⇒ `null`(调用方据 {@link allow} 发 405)。 */ readonly decl: RouteDispatchDecl | null; readonly params: Readonly>; /** 本段里**路径**命中的每一行的 `methods` 并集(按声明序去重;空 = 这条路径在本段一行都不占)。 */ readonly allow: readonly RouteMethod[]; } /** * 在一组可分派行里按「**精确 → 形状**,同类按声明序」找路径命中,再按方法过滤。 * * 🔴 **路径命中不等于就是它**:方法不在这一行的 `methods` 里就继续往后找(`GET /v1/images/bakes` * 落 `image-by-profile` 而不是只认 `POST` 的 `image-bake-submit`,与改前域内 if 链的落点逐字同)。 * 全部路径命中行的方法并集就是 `Allow` —— 并集按**路径**取而不是按行,所以同一条路径住着两只域时 * (`/v1/tasks` 的 GET 在 trace-usage、POST 在 tasks)405 的 `Allow` 是两行之和。 */ export declare function lookupRoute(decls: readonly RouteDecl[], url: string, method: string): RouteLookup; //# sourceMappingURL=route-table.d.ts.map