@sema-agent/server 7.79.1 → 7.79.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,13 +1,14 @@
1
1
  /**
2
- * 请求流水线(S-147 切片:`http/server.ts` 的 `handle()` 1130–1568 与它的模块级分类词表逐字搬家,正文一字未改)。
2
+ * 请求流水线(S-147 切片:`http/server.ts` 的 `handle()` 与它的模块级词表搬家;S-146 再把「这是什么路由」
3
+ * 整件事交给 `./route-table.ts`)。
3
4
  *
4
- * 住在这里的三样东西是一件事的三个面:①`handle()` —— admission 计数 / 附着登记 / CORS / /health / /metrics /
5
- * 鉴权桥 / 全局服务凭据门 / drain 与 roster-pending 两道 503 / 35 只域调用链;②`ROUTE_DOMAINS` —— 那条链的
6
- * 域名册(`test/route-shape-roster.test.ts` 层D 从 handle() 真源码抽序与它对拍);③分类谓词族
7
- * (`routeLabel` / `isBillableSubmitPath` / `isCredentialGatedRewrite` / `isMethodDispatchedSubmitPath` /
8
- * `hasServiceAuth` / `cors` / `PROCESS_STARTED_AT_MS`)—— 流水线判「这条 url 吃哪几道门、记哪个标签」的全部词表。
9
- * 留在 server.ts 则本文件必须值 import 组合根 ⇒ 闭合运行时装载环;设计稿 §5(S-146 第二刀)也正是要把
10
- * ②③ 合成一张 `ROUTES` 声明表,那张表的家就是这里。公面五名由 server.ts **re-export**,消费方 import 零改动。
5
+ * 住在这里的是**一条请求从进门到落地**的全过程,一件事一遍:admission 计数 / 附着登记 / CORS / /health /
6
+ * /metrics / 鉴权桥 / 全局服务凭据门 / 无凭据写门 / drain 与 roster-pending 两道 503 / 破坏性会话写门 /
7
+ * 两段分派 / 全局 404。
8
+ *
9
+ * 🔴 S-146 之后本文件**不再持有任何一份路由名单**:分派序、门侧、标签、三只门的判据全部读
10
+ * `ROUTES` / `ROUTE_DECLARATIONS`(见 route-table.ts 头注)。三道门也不再「按行号插在 37 只 if 中间」——
11
+ * 它们在门前段与门后段之间**恒定一处**执行,三段代码逐字未动,只是不再被域调用夹着。
11
12
  *
12
13
  * ctx 字段 4 只(`deps` / `counters` / `noteActivity` / `routeCtxBase`),机器算(closure-free-vars)与手抄一致。
13
14
  * `openResponses` / `lastAttachEndedAt` 是 handle 自己写的外层 `let`/`Set`,随它一起进工厂 —— 这样那两行赋值
@@ -17,75 +18,10 @@ import type { IncomingMessage, ServerResponse } from "node:http";
17
18
  import type { ServiceConfig } from "../config-types.js";
18
19
  import type { RouteCtxBase, RunRegistry } from "./route-ctx.js";
19
20
  import type { FlatServiceDeps } from "./server.js";
20
- /** POST endpoints that trigger BILLABLE work — the fail-closed auth guard must cover ALL of them (council: the
21
- * guard's inline list had drifted from the handlers and missed `/v1/approvals/:id/decide`, which resumes a run
22
- * via resumeCheckpoint/store.decide → paid tokens). Keep this in sync when adding a billable POST route. */
23
- /**
24
- * 「这条 url **是**计费提交面,但它到底提不提交由**请求体里的方法**决定」—— 目前只有 A2A 的
25
- * JSON-RPC 端点(单 URL 承载 `message/send` 写 + `tasks/get` 读,因为 agent card 只能公告一个 `url`)。
26
- *
27
- * 用途:两道**可用性** 503(drain / model-roster-pending)按 url 一刀切会把 `tasks/get` 这条纯读一起
28
- * 关掉,而单副本部署恰好在重启/依赖降级窗口里最需要取回已完成的结果(codex 轮2 R2-3,验真后修)。
29
- * 本谓词让 `handle()` 对这条 url **跳过**那两道,改由域模块在 `message/send` 分支里施加同样的两道
30
- * (`routes/a2a-serve.ts` 的 `submitUnavailable`,文案逐字同源)。
31
- *
32
- * 🔴 **只豁免那两道,不豁免安全轴**:`isBillableSubmitPath` 本身**不变**(它仍答 true),所以
33
- * ① 无 service token 的部署上那道 fail-closed 503 照旧罩着这条 url —— 它判的是「这台机能不能收写」,
34
- * 与方法无关,而且门必须在解析请求体**之前**;
35
- * ② billable 申明门钉的仍是那只纯谓词(表里 `/v1/a2a` 依旧 billable=true)。
36
- * 新成员进本表 = 承诺「该域在自己的方法分支里补齐那两道」,漏补就是一条静默旁路;所以本表只该有
37
- * 「单 URL 多方法」这一种成员,加一条要连同它的域内补门一起过评审。
38
- */
39
- export declare function isMethodDispatchedSubmitPath(url: string): boolean;
40
21
  /** 「这台部署配了任何一把 service 凭证吗」—— fail-closed 写门族的真值源。**单一属主**:`handle()` 的
41
22
  * 凭证门、[ref] 的 parked 赎回席、以及 [ref] 的 consolidation 阀门启动期告警读同一句(手抄迟早
42
23
  * 只改一处,而这条判据决定的是「无凭证部署上写口开不开」)。导出仅为最后一个消费点(main.ts)。 */
43
24
  export declare function hasServiceAuth(config: ServiceConfig): boolean;
44
- export declare function isBillableSubmitPath(url: string): boolean;
45
- /**
46
- * [ref]([ref] 件5)—— **改写门**:不烧模型、但在**没有任何 service credential** 的部署形下必须与
47
- * billable / bake 两族一样 fail-closed 的写门。谓词与 `isBillableSubmitPath` **刻意分开**:这几扇门
48
- * `billable=false`(零模型工作),塞进那张名单会让它们连带吃 drain 与 model-roster-pending 两道 503 ——
49
- * 而收编与规则读写在排空期/roster 未落时做完全无害。
50
- *
51
- * 判据(两个合取项,缺一不进本表)= 「授权的唯一输入是 principal 头」∧「动作是**持久改写**」:
52
- * · `POST /v1/adoption` —— operator-only 的一次性部署级动作,把一个 principal 名下**每一行**重写到另一个
53
- * 身份;授权判据 `explicitOperatorOk(gatedPrincipal(...))`。
54
- * · `POST /v1/rules/cc-import/*` —— principal lane 的**持久 allow 规则**铸造口;一条持久 allow 规则对后续
55
- * 同命令的 classify 档 ask 常驻消音(core [ref] 之后仍然如此,只是不再消 mandated 的那些)。
56
- * · `DELETE /v1/rules` —— 同一个规则店的**撤销**口。方向相反(收紧)但同样是持久改写,而且它多一条
57
- * operator 超范围域(`?principal=` / body 的 `principal` 让 operator 收回**任一**租户的规则)⇒ 自造一个
58
- * 列在 `OPERATOR_PRINCIPALS` 里的头,就能把每一位租户的规则一次清空。
59
- * 无 service token 的部署上 `verifiedPrincipal` 走的是「BFF/gated:头已由上游验过」那一支,而这一形恰恰
60
- * 是**没有**那个上游 —— 于是自造一个头就能收编别人、替任意租户种下常驻放行、或替他们全删。bake 门当年
61
- * 补的就是同一句话,`isDestructiveSessionWrite` 从逐条路由白名单改成族判定也是同一句话(那次的措辞逐字是
62
- * 「a forged principal header alone could **tighten/DoS** a session's tools」——撤销口落的正是 DoS 那一半)。
63
- *
64
- * 🔴 **方法感知**(codex 交叉复审 R2-[high],验真后修):撤销走的是 `DELETE`,而本门最初只挂在 `POST` 的
65
- * 合取里 —— 于是一个自称「持久改写」的谓词把爆炸半径最大的那条动词漏在门外,正是 `isDestructiveSessionWrite`
66
- * 当年被抓到的同一形。签名因此收方法,与那只谓词逐字同形。
67
- * 🔴 读面**原则上**不进本表:`GET /v1/adoption/:id` 与 `GET /v1/rules` 各有自己的**属主门**兜底,本门只拦写。
68
- *
69
- * 🔴 **第二个合取项的加宽**(codex 交叉复审 [high],[ref] 提货批,验真后修)——判据现在是
70
- * 「持久改写 **或** 授权唯一输入是 principal 头的**跨租户治理读**」。加宽是为了收进 [ref] v2-c 的两口:
71
- * · `POST /v1/memory/import` —— 直接落在**原**判据上,而且是本仓最深的那一种持久改写:它把别处铸的
72
- * 条目 / 挑战 / 污染标记 / 托管链落进本部署的记忆平面。自造一个在册 operator 头 ⇒ 一份**不当调用方自制的
73
- * 治理史**成为本店的账;而记忆是模型**每次挂载都读**的面,比种一条常驻 allow 规则更深(那条只影响
74
- * ask 会不会问,这条影响模型相信什么)。
75
- * · `POST /v1/memory/export` —— 是**读**,按原措辞本不该进。但被点名豁免的那两条读各有属主门兜底,
76
- * 而本条**没有**:它的授权判据就是 `explicitOperatorOk`,过了就是**全库**——一次调用取走每个 scope 的
77
- * 条目正文 + 托管链 + 跨会话污染标记 + lineage。把一次全租户记忆外泄留在门外、只为守住「本门只拦写」
78
- * 这句措辞,是把措辞看得比它要保护的东西更重。加宽后的判据仍然是**闭**的(不是「写门以外都进」):
79
- * 它要求「授权的唯一输入是那个头」∧「一次调用的爆炸半径跨租户」,`GET /v1/rules` 那种属主门读不满足。
80
- * 行为面钉:`test/rewrite-door-service-token-gate.test.ts`(六扇门各一正一反 + 逃生口格 + 读面负控)。
81
- */
82
- export declare function isCredentialGatedRewrite(method: string, url: string): boolean;
83
- /** Stable, low-cardinality route label for metrics/logs (ids collapsed to `:id`).
84
- * [[ref]] 字面量**先于**模式:此前模式先查,五条精确路由被形状桶吞掉(`/v1/approvals/stream`
85
- * 落 ":id"、`/v1/images/{bakes,select,register}` 落 ":profile"、`/v1/images/bakes/claim` 落
86
- * "bakes/:id")——长连接 stream 的 duration 混进 :id 桶正是 fleet/stream 案的同族错桶病。
87
- * 精确匹配恒比形状匹配更对,序修零反例(billable-route-declaration 门逐样本钉)。 */
88
- export declare function routeLabel(_method: string, url: string): string;
89
25
  export declare function createDispatch(ctx: {
90
26
  deps: FlatServiceDeps;
91
27
  noteActivity: (url: string) => void;
@@ -2,221 +2,13 @@ import { createHash } from "node:crypto";
2
2
  import { setSsoPrincipal, ssoVerifiedPrincipal, setSsoScope, isDestructiveSessionWrite, PRINCIPAL_TOKEN_HEADER, APPROVAL_MAC_HEADER, APPROVAL_MAC_KID_HEADER } from "../security.js";
3
3
  import { publicStoreProbeError } from "../store-live-probe.js";
4
4
  import { looksLikeJwt } from "@sema-agent/settings-schema/api/auth-bridge";
5
- import { handleCapabilities } from "./routes/capabilities.js";
6
- import { handleObservability } from "./routes/observability.js";
7
- import { handleDiagnostics } from "./routes/diagnostics.js";
8
- import { handleAdoption } from "./routes/adoption.js";
9
- import { handleRetention } from "./routes/retention-ops.js";
10
- import { handleAdminDrain } from "./routes/admin-drain.js";
11
- import { handleDevices } from "./routes/devices.js";
12
- import { handleAdminConfigRefresh } from "./routes/admin-config-refresh.js";
13
- import { handleConfigCatalog } from "./routes/config-catalog.js";
14
- import { handleMemoryOptOut } from "./routes/admin-memory-optout.js";
15
- import { handleMemoryPolicy } from "./routes/memory-policy.js";
16
- import { handleMemoryBundle } from "./routes/memory-bundle.js";
17
- import { handleMemoryCompliance } from "./routes/memory-compliance.js";
18
- import { handleMemoryOrigin } from "./routes/memory-origin.js";
19
- import { handleMemoryConsolidation } from "./routes/memory-consolidation.js";
20
- import { handleSharedMemory } from "./routes/shared-memory.js";
21
- import { handleRules } from "./routes/rules.js";
22
- import { handleSessionsList } from "./routes/sessions-list.js";
23
- import { handleAgentRoster } from "./routes/agents-roster.js";
24
- import { handleSessions } from "./routes/sessions.js";
25
- import { handleSessionSync } from "./routes/session-sync.js";
26
- import { handleAttachments } from "./routes/attachments.js";
27
- import { handleFleet } from "./routes/fleet.js";
28
- import { handleA2aServe, handleA2a } from "./routes/a2a-serve.js";
29
- import { handleTraceUsage } from "./routes/trace-usage.js";
30
- import { handleWorkflows, handleWorkflowAgentSteer } from "./routes/workflows.js";
31
- import { handleNotifyWake, SESSION_WAKE_RE } from "./routes/notify-wake.js";
32
- import { handleApprovalsAssistant, ASSISTANT_RESUME_RE, ASSISTANT_PLAN_REVIEW_RE } from "./routes/approvals-assistant.js";
33
- import { handleRuns, handleRunVerbs, RUN_SUBAGENT_RESUME_RE } from "./routes/runs.js";
34
- import { handleRunMemoryCapture } from "./routes/run-memory-capture.js";
35
- import { handleSideQuery } from "./routes/side-query.js";
36
- import { handleTasks } from "./routes/tasks.js";
37
- import { handleLeader } from "./routes/leader.js";
38
- import { handleImages } from "./routes/images.js";
5
+ import { PRE_CREDENTIAL_ROUTES, POST_CREDENTIAL_ROUTES, routeLabel, isBillableSubmitPath, isCredentialGatedRewrite, isMethodDispatchedSubmitPath } from "./route-table.js";
39
6
  import { sendJson, sendError, isSseResponse } from "./send.js";
40
7
  import { authorized, systemFor } from "./principal-gate.js";
41
8
  const PROCESS_STARTED_AT_MS = Date.now() - Math.round(process.uptime() * 1000);
42
- const ROUTE_DOMAINS = [
43
- handleTraceUsage,
44
- handleWorkflows,
45
- handleFleet,
46
- handleA2aServe,
47
- handleCapabilities,
48
- handleSideQuery,
49
- handleTasks,
50
- handleRuns,
51
- handleA2a,
52
- handleWorkflowAgentSteer,
53
- handleRunVerbs,
54
- handleRunMemoryCapture,
55
- handleLeader,
56
- handleImages,
57
- handleApprovalsAssistant,
58
- handleObservability,
59
- handleDiagnostics,
60
- handleAdoption,
61
- handleRetention,
62
- handleAdminDrain,
63
- handleDevices,
64
- handleAdminConfigRefresh,
65
- handleConfigCatalog,
66
- handleMemoryOptOut,
67
- handleMemoryPolicy,
68
- handleMemoryBundle,
69
- handleMemoryCompliance,
70
- handleMemoryOrigin,
71
- handleMemoryConsolidation,
72
- handleRules,
73
- handleSharedMemory,
74
- handleSessionsList,
75
- handleAgentRoster,
76
- handleSessions,
77
- handleSessionSync,
78
- handleAttachments,
79
- handleNotifyWake,
80
- ];
81
- void ROUTE_DOMAINS;
82
- export function isMethodDispatchedSubmitPath(url) {
83
- return url === "/v1/a2a";
84
- }
85
9
  export function hasServiceAuth(config) {
86
10
  return Boolean(config.authToken) || Object.keys(config.authTokens ?? {}).length > 0;
87
11
  }
88
- export function isBillableSubmitPath(url) {
89
- return (url === "/v1/side-query" ||
90
- url === "/v1/tasks" ||
91
- url === "/v1/tasks/stream" ||
92
- url === "/v1/runs" ||
93
- url === "/v1/leader" ||
94
- url === "/v1/a2a" ||
95
- url === "/v1/admin/memory/consolidation/run" ||
96
- ASSISTANT_RESUME_RE.test(url) ||
97
- ASSISTANT_PLAN_REVIEW_RE.test(url) ||
98
- RUN_SUBAGENT_RESUME_RE.test(url) ||
99
- SESSION_WAKE_RE.test(url) ||
100
- url.startsWith("/v1/approvals"));
101
- }
102
- export function isCredentialGatedRewrite(method, url) {
103
- const m = method.toUpperCase();
104
- if (m === "POST") {
105
- return (url === "/v1/adoption" ||
106
- url.startsWith("/v1/rules/cc-import/") ||
107
- url.startsWith("/v1/rules/local-import/") ||
108
- url === "/v1/memory/import" ||
109
- url === "/v1/memory/export" ||
110
- url === "/v1/memory/erase" ||
111
- url.startsWith("/v1/memory/origin/entries/") ||
112
- url.startsWith("/v1/devices/"));
113
- }
114
- if (m === "PUT")
115
- return url === "/v1/admin/memory-optout" || url.startsWith("/v1/admin/memory-optout/");
116
- if (m === "DELETE")
117
- return url === "/v1/rules" || url.startsWith("/v1/admin/memory-optout/");
118
- if (m === "GET") {
119
- return (/^\/v1\/memory\/entries\/[^/]+\/provenance$/.test(url) ||
120
- url === "/v1/memory/origin/external" ||
121
- url === "/v1/memory/origin/clearances" ||
122
- url === "/v1/admin/memory-optout" ||
123
- url === "/v1/devices" ||
124
- /^\/v1\/devices\/sessions\/[^/]+$/.test(url));
125
- }
126
- return false;
127
- }
128
- const ROUTE_LABEL_PATTERNS = [
129
- [/^\/v1\/runs\/[^/]+\/steer$/, "/v1/runs/:id/steer"],
130
- [/^\/v1\/runs\/[^/]+\/memory\/capture-optout$/, "/v1/runs/:id/memory/capture-optout"],
131
- [/^\/v1\/runs\/[^/]+\/interrupt$/, "/v1/runs/:id/interrupt"],
132
- [/^\/v1\/runs\/[^/]+\/detach$/, "/v1/runs/:id/detach"],
133
- [/^\/v1\/runs\/[^/]+\/cancel$/, "/v1/runs/:id/cancel"],
134
- [/^\/v1\/runs\/[^/]+\/compact$/, "/v1/runs/:id/compact"],
135
- [/^\/v1\/runs\/[^/]+\/events$/, "/v1/runs/:id/events"],
136
- [/^\/v1\/runs\/[^/]+\/subagents\/[^/]+\/(steer|resume|output|stream)$/, "/v1/runs/:id/subagents/:sub/*"],
137
- [/^\/v1\/runs\/[^/]+\/tasks\/[^/]+\/(output|stop)$/, "/v1/runs/:id/tasks/:tid/*"],
138
- [/^\/v1\/runs\/[^/]+$/, "/v1/runs/:id"],
139
- [/^\/v1\/images\/bakes\/[^/]+\/events$/, "/v1/images/bakes/:id/events"],
140
- [/^\/v1\/images\/bakes\/[^/]+\/cancel$/, "/v1/images/bakes/:id/cancel"],
141
- [/^\/v1\/images\/bakes\/[^/]+\/claim$/, "/v1/images/bakes/:id/claim"],
142
- [/^\/v1\/images\/bakes\/[^/]+\/ingest$/, "/v1/images/bakes/:id/ingest"],
143
- [/^\/v1\/images\/bakes\/[^/]+$/, "/v1/images/bakes/:id"],
144
- [/^\/v1\/images\/digests\/[^/]+$/, "/v1/images/digests/:digest"],
145
- [/^\/v1\/images\/[^/]+$/, "/v1/images/:profile"],
146
- [/^\/v1\/approvals\/[^/]+\/decide$/, "/v1/approvals/:id/decide"],
147
- [/^\/v1\/approvals\/[^/]+\/exemptions(\/[^/]+)?$/, "/v1/approvals/:id/exemptions"],
148
- [/^\/v1\/sessions\/[^/]+\/(wake|fork|init|notify|events|head|mcp|a2a|policy|settings)$/, "/v1/sessions/:id/$verb"],
149
- [/^\/v1\/admin\/memory-optout\/[^/]+$/, "/v1/admin/memory-optout/:principal"],
150
- [/^\/v1\/sessions\/[^/]+\/memory\/erase$/, "/v1/sessions/:id/memory/erase"],
151
- [/^\/v1\/sessions\/[^/]+\/memory-status$/, "/v1/sessions/:id/memory-status"],
152
- [/^\/v1\/sessions\/[^/]+\/sync\/(manifest|entries|plan)$/, "/v1/sessions/:id/sync/$verb"],
153
- [/^\/v1\/sessions\/[^/]+\/sync\/import(\/[^/]+\/entries)?$/, "/v1/sessions/:id/sync/import"],
154
- [/^\/v1\/sessions\/[^/]+\/sync\/blobs(\/[^/]+)?$/, "/v1/sessions/:id/sync/blobs"],
155
- [/^\/v1\/sessions\/[^/]+\/sync\/history\/blobs\/[^/]+$/, "/v1/sessions/:id/sync/history/blobs/:hash"],
156
- [/^\/v1\/sessions\/[^/]+\/workspace(\/[^/]+(\/(tree|file|archive))?)?$/, "/v1/sessions/:id/workspace"],
157
- [/^\/v1\/sessions\/[^/]+$/, "/v1/sessions/:id"],
158
- [/^\/v1\/assistant\/tasks\/[^/]+\/preempt$/, "/v1/assistant/tasks/:id/preempt"],
159
- [/^\/v1\/assistant\/tasks\/[^/]+\/resume$/, "/v1/assistant/tasks/:id/resume"],
160
- [/^\/v1\/assistant\/tasks\/[^/]+\/plan_review$/, "/v1/assistant/tasks/:id/plan_review"],
161
- [/^\/v1\/elicitations\/[^/]+\/respond$/, "/v1/elicitations/:id/respond"],
162
- [/^\/v1\/questions\/[^/]+\/respond$/, "/v1/questions/:id/respond"],
163
- [/^\/v1\/tool-approvals\/[^/]+\/respond$/, "/v1/tool-approvals/:id/respond"],
164
- [/^\/v1\/workflows\/[^/]+\/agents\/[^/]+\/steer$/, "/v1/workflows/:id/agents/:aid/steer"],
165
- [/^\/v1\/workflows\/[^/]+\/(stream|journal)$/, "/v1/workflows/:id/$sub"],
166
- [/^\/v1\/workflows\/[^/]+$/, "/v1/workflows/:id"],
167
- [/^\/v1\/tasks\/[^/]+\/asks\/[^/]+\/decision$/, "/v1/tasks/:id/asks/:askId/decision"],
168
- [/^\/v1\/tasks\/[^/]+\/tool-results\/[^/]+$/, "/v1/tasks/:id/tool-results/:ref"],
169
- [/^\/v1\/tasks\/[^/]+\/(turns|stream|artifacts)$/, "/v1/tasks/:id/$sub"],
170
- [/^\/v1\/leader\/[^/]+$/, "/v1/leader/:id"],
171
- [/^\/v1\/devices\/sessions\/[^/]+\/rebind$/, "/v1/devices/sessions/:id/rebind"],
172
- [/^\/v1\/devices\/sessions\/[^/]+$/, "/v1/devices/sessions/:id"],
173
- [/^\/v1\/attachments\/[^/]+$/, "/v1/attachments/:id"],
174
- [/^\/v1\/capabilities\/scenarios\/[^/]+$/, "/v1/capabilities/scenarios/:name"],
175
- [/^\/v1\/adoption\/[^/]+$/, "/v1/adoption/:id"],
176
- [/^\/v1\/ops\/retention\/holds\/[^/]*$/, "/v1/ops/retention/holds/:domain"],
177
- [/^\/v1\/memory\/sync\/[^/]+$/, "/v1/memory/sync/:scope"],
178
- [/^\/v1\/memory\/entries\/[^/]+\/provenance$/, "/v1/memory/entries/:entryId/provenance"],
179
- [/^\/v1\/memory\/origin\/entries\/[^/]+\/clear$/, "/v1/memory/origin/entries/:entryId/clear"],
180
- [/^\/v1\/usage\/(summary|series|breakdown)$/, "/v1/usage/$sub"],
181
- [/^\/v1\/shared-memory\/stores\/[^/]+\/documents$/, "/v1/shared-memory/stores/:store/documents"],
182
- [/^\/v1\/shared-memory\/stores\/[^/]+\/document$/, "/v1/shared-memory/stores/:store/document"],
183
- ];
184
- const ROUTE_LABEL_LITERALS = new Set([
185
- "/health", "/metrics", "/metrics/summary", "/metrics/plan-cache",
186
- "/v1/models", "/v1/tasks", "/v1/tasks/stream", "/v1/tasks/source-summary",
187
- "/v1/runs", "/v1/sessions", "/v1/approvals", "/v1/approvals/stream",
188
- "/v1/assistant/inbox", "/v1/assistant/tasks", "/v1/usage", "/v1/policy", "/v1/capabilities",
189
- "/v1/images", "/v1/images/bakes", "/v1/images/bakes/claim", "/v1/images/select", "/v1/images/register",
190
- "/v1/workflows", "/v1/attachments", "/v1/leader", "/v1/outcomes", "/v1/side-query",
191
- "/v1/devices",
192
- "/v1/fleet/stream",
193
- "/v1/memory/export", "/v1/sendfile-links",
194
- "/v1/memory/import",
195
- "/v1/memory/erase",
196
- "/v1/memory/origin/external", "/v1/memory/origin/clearances",
197
- "/v1/shared-memory/stores",
198
- "/v1/diagnostics/wiring",
199
- "/v1/adoption",
200
- "/v1/admin/drain",
201
- "/v1/admin/config/refresh",
202
- "/v1/config/catalog",
203
- "/v1/admin/memory-optout",
204
- "/v1/admin/memory/consolidation/run", "/v1/admin/memory/consolidation",
205
- "/v1/rules/cc-import/prepare", "/v1/rules/cc-import/redeem",
206
- "/v1/rules/local-import/prepare", "/v1/rules/local-import/redeem",
207
- "/v1/rules",
208
- "/.well-known/agent-card.json", "/v1/a2a",
209
- "/v1/agents/roster",
210
- "/v1/ops/retention/audit",
211
- ]);
212
- export function routeLabel(_method, url) {
213
- if (ROUTE_LABEL_LITERALS.has(url))
214
- return url;
215
- for (const [re, label] of ROUTE_LABEL_PATTERNS)
216
- if (re.test(url))
217
- return label;
218
- return "other";
219
- }
220
12
  function cors(res, req, origins, principalHeader) {
221
13
  if (!origins || origins.length === 0)
222
14
  return;
@@ -250,6 +42,15 @@ export function createDispatch(ctx) {
250
42
  };
251
43
  deps.drainState.lastAttachEndedAt = () => lastAttachEndedAt;
252
44
  }
45
+ async function dispatchSegment(rows, req, res, url, ctx) {
46
+ for (const row of rows) {
47
+ if (row.enabled !== undefined && !row.enabled(deps))
48
+ continue;
49
+ if (await row.handler(req, res, url, ctx))
50
+ return true;
51
+ }
52
+ return false;
53
+ }
253
54
  async function handle(req, res) {
254
55
  const startedAt = Date.now();
255
56
  const method = req.method ?? "GET";
@@ -379,13 +180,7 @@ export function createDispatch(ctx) {
379
180
  }
380
181
  }
381
182
  }
382
- if (await handleTraceUsage(req, res, url, ctx))
383
- return;
384
- if (await handleWorkflows(req, res, url, ctx))
385
- return;
386
- if (await handleFleet(req, res, url, ctx))
387
- return;
388
- if (await handleA2aServe(req, res, url, ctx))
183
+ if (await dispatchSegment(PRE_CREDENTIAL_ROUTES, req, res, url, ctx))
389
184
  return;
390
185
  let source = null;
391
186
  const anyServiceAuth = hasServiceAuth(deps.config);
@@ -426,71 +221,7 @@ export function createDispatch(ctx) {
426
221
  sendError(res, 503, "auth.service_token_required", "this worker requires a service auth token (set SERVICE_AUTH_TOKEN) before accepting destructive session writes (policy / sync-import / fork / delete)");
427
222
  return;
428
223
  }
429
- if (await handleCapabilities(req, res, url, ctx))
430
- return;
431
- if (await handleSideQuery(req, res, url, ctx))
432
- return;
433
- if (await handleTasks(req, res, url, ctx))
434
- return;
435
- if (await handleRuns(req, res, url, ctx))
436
- return;
437
- if (await handleA2a(req, res, url, ctx))
438
- return;
439
- if (await handleWorkflowAgentSteer(req, res, url, ctx))
440
- return;
441
- if (await handleRunVerbs(req, res, url, ctx))
442
- return;
443
- if (await handleRunMemoryCapture(req, res, url, ctx))
444
- return;
445
- if (await handleLeader(req, res, url, ctx))
446
- return;
447
- if (await handleImages(req, res, url, ctx))
448
- return;
449
- if (await handleApprovalsAssistant(req, res, url, ctx))
450
- return;
451
- if (await handleObservability(req, res, url, ctx))
452
- return;
453
- if (await handleDiagnostics(req, res, url, ctx))
454
- return;
455
- if (await handleAdoption(req, res, url, ctx))
456
- return;
457
- if (await handleRetention(req, res, url, ctx))
458
- return;
459
- if (await handleAdminDrain(req, res, url, ctx))
460
- return;
461
- if (await handleDevices(req, res, url, ctx))
462
- return;
463
- if (await handleAdminConfigRefresh(req, res, url, ctx))
464
- return;
465
- if (await handleConfigCatalog(req, res, url, ctx))
466
- return;
467
- if (await handleMemoryOptOut(req, res, url, ctx))
468
- return;
469
- if (await handleMemoryPolicy(req, res, url, ctx))
470
- return;
471
- if (await handleMemoryBundle(req, res, url, ctx))
472
- return;
473
- if (await handleMemoryCompliance(req, res, url, ctx))
474
- return;
475
- if (await handleMemoryOrigin(req, res, url, ctx))
476
- return;
477
- if (deps.memoryConsolidation && (await handleMemoryConsolidation(req, res, url, ctx)))
478
- return;
479
- if (await handleRules(req, res, url, ctx))
480
- return;
481
- if (deps.sharedMemoryStore && deps.orgMemoryDirectory && (await handleSharedMemory(req, res, url, ctx)))
482
- return;
483
- if (await handleSessionsList(req, res, url, ctx))
484
- return;
485
- if (await handleAgentRoster(req, res, url, ctx))
486
- return;
487
- if (await handleSessions(req, res, url, ctx))
488
- return;
489
- if (await handleSessionSync(req, res, url, ctx))
490
- return;
491
- if (await handleAttachments(req, res, url, ctx))
492
- return;
493
- if (await handleNotifyWake(req, res, url, ctx))
224
+ if (await dispatchSegment(POST_CREDENTIAL_ROUTES, req, res, url, ctx))
494
225
  return;
495
226
  sendError(res, 404, "not_found.route", "not found");
496
227
  }
@@ -0,0 +1,113 @@
1
+ /**
2
+ * **路由声明表**(S-146,[ref] 波 2 第二刀)—— 「这是什么路由」这件事在本仓的**唯一属主**。
3
+ *
4
+ * ## 病(S-146 债行原文的读数)
5
+ * 同一件事此前有五份写法、两道扫源码文本的对账门,而那两道门已经**反向塑形了源码**:表里必须写
6
+ * 字面量不许引常量(否则门的正则抓不到)、注释里不许出现某两个字符(否则门的非贪婪捕获提前收尾)。
7
+ * ① 分派序 = `handle()` 里 37 只 `if (await handleX(...)) return;`,鉴权/排空/破坏性写三道门按**行号**
8
+ * 插在链中间(4 只 handler 落在凭据门前);
9
+ * ② `ROUTE_DOMAINS` = 同一条链的域名册,靠一道从 `handle()` 源码文本抽序的门与 ① 对拍(表曾漏员又乱序);
10
+ * ③ `ROUTE_LABEL_PATTERNS` / `ROUTE_LABEL_LITERALS` = metrics/日志的标签名册;
11
+ * ④ `isBillableSubmitPath` / `isMethodDispatchedSubmitPath` = 三道 503 计费门的 url 名单;
12
+ * ⑤ `isCredentialGatedRewrite` = 无凭据部署上的改写门 url×方法 名单。
13
+ * 五份各自漂过:wake 不在 ④ 里(三道门对一条烧模型的腿全不生效)、`handleDiagnostics` 从不在 ② 里、
14
+ * `/v1/fleet/stream` 整条对 ③ 隐形。
15
+ *
16
+ * ## 形(规则少了几条)
17
+ * **一行 = 一条路由事实**,两张表:
18
+ * · `ROUTES` —— **行序 = 分派序**,`position` 说它在全局服务凭据门的哪一侧。门不再「按行号插在链中间」,
19
+ * 而是在 `pre` 段与 `post` 段之间**恒定一处**执行 ⇒ ① 与 ② 合一(名册就是分派序本身,两者再不可能漂),
20
+ * 那道「从 handle() 源码抽序对拍」的门随之删掉 —— 不是放松,是它守的事变成了结构上不可违反。
21
+ * ⚠️ 刻意**不引入 matcher**:handler 自己判 url 这一点一个字不动(改它等于重写 34 个域模块的判路)。
22
+ * · `ROUTE_DECLARATIONS` —— ③④⑤ 合成一张;一行声明它的匹配式与它**真有**的性质
23
+ * (`billable` / `credentialGated` / `methodDispatched`),没有该性质就不写那一列(零默认值)。
24
+ *
25
+ * 判据两条,逐字:
26
+ * · **标签取首命中**(优先序):精确行先于形状行([[ref]] 的结论,这里是结构性的而不是靠行序),
27
+ * 形状行按声明序;没有任何行给标签 ⇒ `"other"`。
28
+ * · **门取并集**(宁紧):一条 url 只要**有一行**声明了某性质,它就有该性质 —— 这正是旧谓词那几个
29
+ * `||` 的逐字同义,也是「前缀行比标签行宽」这件事的正解(`/v1/devices/` 下将来长出的任何写动词
30
+ * 天然在改写门内,不必等谁想起来补一行标签)。
31
+ *
32
+ * 新增一条路由 = 新建 `routes/x.ts` + 本表加一行(+ `test/route-order-freeze.test.ts` 的冻结表加一行,
33
+ * 那是 position 的外部对拍源)。`position` 与匹配式都是必填 —— 忘了就是**编译红**,不是静默 404。
34
+ *
35
+ * 🔴 **旧禁令已解除**:标签表此前必须「写字面量不引常量」、注释里不许出现某两个字符 —— 那是两道
36
+ * 扫源码文本的门反向塑形源码的产物(它们的捕获正则抓不到常量、非贪婪捕获会被那对字符提前截断)。
37
+ * 两道门都改成读本表之后,这条禁令没有了对象,随它的 11 处行注一并删;要引常量就引(`routes/*.ts`
38
+ * 的 `*_PATH` 常量本来就是那些路径的单一属主)。
39
+ *
40
+ * 🔴 本文件是**叶模块**:只 import `routes/*.ts` 的 handler 与几只单源正则、以及类型。组合根与流水线
41
+ * (`server.ts` / `dispatch.ts`)一律反过来 import 它,值边单向,`test/module-cycle-gate.test.ts` 看守。
42
+ */
43
+ import type { FlatServiceDeps } from "./server.js";
44
+ import type { RouteHandler } from "./route-ctx.js";
45
+ /** 一只域 handler 相对**全局服务凭据门**的位置。门前 = 未鉴权可达(每一只都要单独论证),门后 = 先过凭据。 */
46
+ export type RoutePosition = "pre-credential" | "post-credential";
47
+ export interface RouteRow {
48
+ /** 域入口(`routes/*.ts` 的 `handleX`)。签名一致性是编译期钉:入口漂了形 = 编译红,不是运行时静默 404。 */
49
+ readonly handler: RouteHandler;
50
+ /** 必填 —— 新增路由必须**显式**决定它在凭据门的哪一侧。 */
51
+ readonly position: RoutePosition;
52
+ /** 域名(kebab;= `routes/` 下的模块名)。评审把本表当「域全集」读时它是那份清单的名字列,
53
+ * `test/route-shape-roster.test.ts` 层E 的能力位词干也取自这里 —— 域的身份是声明出来的,
54
+ * 不是从函数名反推的。函数真名与它的一致性由 `route-order-freeze` 自检格钉住。 */
55
+ readonly label: string;
56
+ /** **挂载条件**(只有真有条件的行才写):缺席 = 恒挂载。域头的合取式就是挂载条件 —— 阀门没开 ⇒
57
+ * 请求落到分派器尾的全局 404(**诚实缺席,不是 501**;判据全文在各域模块头注)。 */
58
+ readonly enabled?: (deps: FlatServiceDeps) => boolean;
59
+ }
60
+ export declare const ROUTES: readonly RouteRow[];
61
+ /** 两段(`dispatch.ts` 的分派循环各跑一段)。段内序 = 本表行序;
62
+ * 「门前行不许出现在门后行之后」由 `test/route-order-freeze.test.ts` 钉住。 */
63
+ export declare const PRE_CREDENTIAL_ROUTES: readonly RouteRow[];
64
+ export declare const POST_CREDENTIAL_ROUTES: readonly RouteRow[];
65
+ /** 改写门是**方法感知**的(它的撤销动词是 DELETE)。闭集 —— 加第五个动词是编译红。 */
66
+ export type RouteMethod = "GET" | "POST" | "PUT" | "DELETE";
67
+ interface RouteDeclFacts {
68
+ /** 「这条 url **烧模型**」—— drain / model-roster-pending / 无 service-token 三道 503 的共同判别。
69
+ * 没有这一列 = 不烧模型(零默认值:要它为真必须显式写出来,漏写的方向是**少收一笔账单**而不是
70
+ * 少一道门 —— 反了才是灾难,所以三道门读的是 true 这一侧)。 */
71
+ readonly billable?: true;
72
+ /** 「本条 url 在**没有任何 service credential** 的部署形下,这几个方法必须 fail-closed」。
73
+ * 判据(两个合取项)= 「授权的唯一输入是 principal 头」∧「持久改写,或爆炸半径跨租户的治理读」;
74
+ * 逐口论证在各 `routes/*.ts` 的头注里,本表只记结论。 */
75
+ readonly credentialGated?: readonly RouteMethod[];
76
+ /** 「这条 url **是**计费提交面,但它到底提不提交由**请求体里的方法**决定」—— 单 URL 多方法的
77
+ * JSON-RPC 端点。两道**可用性** 503(drain / roster-pending)按 url 一刀切会把同端点上的纯读一起
78
+ * 关掉,所以分派器对本行**跳过**那两道,改由域模块在写分支里施加同样的两道(文案逐字同源)。
79
+ * 🔴 只豁免那两道,**不豁免安全轴**:`billable` 本身仍为 true,无 service token 那道 fail-closed
80
+ * 503 照旧罩着(它判的是「这台机能不能收写」,与方法无关,且必须在解析请求体之前)。 */
81
+ readonly methodDispatched?: true;
82
+ }
83
+ /**
84
+ * 一行的匹配式三选一(闭集;`path` 行的标签**就是** path 自身,所以它没有 label 列 —— 少一处手抄)。
85
+ * `prefix` 与 `pattern` 行的 label 可缺席:那是**纯门行**(比标签行宽,只供门的并集判据),
86
+ * 缺席 ⇒ 本行不铸任何标签。
87
+ */
88
+ export type RouteDecl = (RouteDeclFacts & {
89
+ readonly path: string;
90
+ }) | (RouteDeclFacts & {
91
+ readonly prefix: string;
92
+ readonly label?: string;
93
+ }) | (RouteDeclFacts & {
94
+ readonly pattern: RegExp;
95
+ readonly label?: string;
96
+ });
97
+ /** 🔴 由 `test/route-shape-roster.test.ts` 棘轮看守:门机器枚举 `src/http/**` 的全部路由模式并断言每条
98
+ * 都映射到非 "other"——新路由不补本表该门当场红(census 抓获本表曾漂掉 56 条模式:session-sync/workspace/
99
+ * workflows/subagents/attachments/images/leader 等整族全折 "other",metrics 不可分桶)。 */
100
+ export declare const ROUTE_DECLARATIONS: readonly RouteDecl[];
101
+ /** Stable, low-cardinality route label for metrics/logs (ids collapsed to `:id`). */
102
+ export declare function routeLabel(_method: string, url: string): string;
103
+ /** POST endpoints that trigger BILLABLE work — the fail-closed auth guard must cover ALL of them. */
104
+ export declare function isBillableSubmitPath(url: string): boolean;
105
+ /** [ref]([ref] 件5)—— **改写门**:不烧模型、但在没有任何 service credential 的部署形下必须
106
+ * 与 billable / bake 两族一样 fail-closed 的写门。与 `isBillableSubmitPath` **刻意分开**:这几扇门
107
+ * `billable=false`(零模型工作),塞进那张名单会让它们连带吃 drain 与 roster-pending 两道 503 ——
108
+ * 而收编与规则读写在排空期/roster 未落时做完全无害。 */
109
+ export declare function isCredentialGatedRewrite(method: string, url: string): boolean;
110
+ /** 见 `RouteDeclFacts.methodDispatched` 的头注(单 URL 多方法端点)。 */
111
+ export declare function isMethodDispatchedSubmitPath(url: string): boolean;
112
+ export {};
113
+ //# sourceMappingURL=route-table.d.ts.map