@sema-agent/server 7.36.0 → 7.37.0
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.
- package/USAGE.md +9 -0
- package/dist/boot/resolve-spec.js +6 -1
- package/dist/boot/runner-deps.d.ts +21 -3
- package/dist/boot/runner-deps.js +31 -4
- package/dist/boot/session-faces.d.ts +3 -0
- package/dist/boot/session-faces.js +11 -2
- package/dist/boot/side-query-lane.d.ts +77 -54
- package/dist/boot/side-query-lane.js +102 -66
- package/dist/boot/stores.d.ts +1 -0
- package/dist/boot/stores.js +19 -1
- package/dist/boot/task-list-lane.d.ts +92 -0
- package/dist/boot/task-list-lane.js +63 -0
- package/dist/bounded-session-map.d.ts +3 -0
- package/dist/bounded-session-map.js +5 -0
- package/dist/capabilities/scenarios.d.ts +30 -2
- package/dist/capabilities/scenarios.js +16 -3
- package/dist/config-types.d.ts +21 -1
- package/dist/config.js +8 -1
- package/dist/hooks/branch-transcript.d.ts +3 -2
- package/dist/hooks/branch-transcript.js +7 -1
- package/dist/http/active-run-conflict.d.ts +43 -0
- package/dist/http/active-run-conflict.js +8 -0
- package/dist/http/route-ctx.d.ts +22 -1
- package/dist/http/routes/approvals-assistant.js +13 -1
- package/dist/http/routes/notify-wake.js +6 -0
- package/dist/http/routes/runs.js +1 -1
- package/dist/http/routes/tasks.js +30 -2
- package/dist/http/server.js +103 -7
- package/dist/index.d.ts +3 -1
- package/dist/index.js +5 -0
- package/dist/main.js +11 -3
- package/dist/memory-posture.d.ts +12 -1
- package/dist/memory-posture.js +2 -0
- package/dist/observability/fail-open.d.ts +9 -1
- package/dist/observability/fail-open.js +9 -1
- package/dist/plugins/memory-engine-pg.js +42 -4
- package/dist/plugins/memory-engine-tidb.js +38 -4
- package/dist/plugins/memory-origin-law.d.ts +69 -0
- package/dist/plugins/memory-origin-law.js +98 -0
- package/dist/plugins/retention-store-sql.d.ts +7 -0
- package/dist/plugins/retention-store-sql.js +24 -0
- package/dist/plugins/task-list-store-sql.d.ts +36 -25
- package/dist/plugins/task-list-store-sql.js +102 -0
- package/dist/run-local.js +14 -5
- package/dist/runs.js +17 -0
- package/dist/trace/engine-notice-wire.d.ts +128 -0
- package/dist/trace/engine-notice-wire.js +256 -0
- package/dist/trace/ledger-events.d.ts +11 -1
- package/dist/trace/ledger-sink.d.ts +17 -0
- package/dist/trace/ledger-sink.js +25 -0
- package/package.json +2 -2
package/USAGE.md
CHANGED
|
@@ -199,6 +199,7 @@ MODEL_CODE_ROLES=default,subagent # 不设=全中立;仅这些角色在「
|
|
|
199
199
|
| `MEMORY_ORG_GRANT_TTL_MS` | `60000`(`[1000, 3600000]`) | 授予/负结果的缓存 TTL = 「有界 LKG」:**新任务/新 resume 腿**的准入判决滞后 ≤ 此值;**在跑任务不受吊销影响**(判决点在 prepare 期) |
|
|
200
200
|
| `MEMORY_ORG_UNAVAILABLE_BACKOFF_MS` | `10000`(`[500, 600000]`) | 目录取不到之后的退避窗(只用于取数失败臂,不用于负结果) |
|
|
201
201
|
| `MEMORY_DELEGATION_EVIDENCE` | 缺省(=引擎自缺省 `static-face`) | **委派臂的证据标准**(core 5.45.0 design/324)。`static-face`=今日行为:委派的 attestation 缺失/未知**且**静态工具面够得着外部内容 ⇒ 标记本会话记忆为已污染(可能性即暴露)。`attested-only`=**只**豁免那一条静态面标记,且**真的豁免了一次**才响亮通告(`engine_notice` 的 `memory.delegation_static_mark_waived`,每个 prepared leg **至多**一次);送达 `external` attestation 照标、非委派的污染类工具照标、委派工具自身被分类为污染的照标。⚠️ 通告**不是 leg 计数器**:不含委派的 leg、污染面没挂载的 leg、送达了 attestation 的委派都不发 —— 看不到通告的常态含义是「没有可豁免的事」,不是「配置坏了」。坏值**启动期拒**。**部署席 only**(TaskSpec 无同名键,任务/受治脚本无法据此放松部署)。⚠️ **已接受的代价**:`attested-only` 下**后台**子代的真实外部接触不标记本会话(其内容经 TaskOutput / 任务通知注入 / AgentTranscript 摘要回流,三条都不带 attestation);异常收尾的前台子代同理。已标记的会话永不回滚清洗。运维读面 = `GET /v1/diagnostics/wiring` 的 `memoryPosture.delegationEvidence`(`null`=本部署未设,引擎自缺省) |
|
|
202
|
+
| `MEMORY_PROVENANCE` | 缺省(=引擎自缺省 `carry`) | **记忆 provenance 总开关**(core 5.46.0 design/336 §13-3)。一个会话被判为**已暴露**之后,它的记忆写怎么处置。`carry`=引擎缺省:**普通**记忆写照常提交,但带上引擎铸的 `origin` 标记(随条目走后端/同步/导出包);指令形文件(feedback / pinned / triggers / applies-when)仍被扣下隔离;派生索引的会话散文照回滚;内容扫描门原样跑(**标记不是豁免**)。`off`=design/336 之前的行为:不铸 origin 标记,已暴露会话的 harvest **一条都不收**(整体隔离候人审)。⚠️ 已提交的标记在编辑时照样带下去 —— `off` 停的是**铸**,从不抹掉已记的事实。与 `MEMORY_DELEGATION_EVIDENCE` **正交**(四种组合全合法):那一键决定**什么时候**判会话为已暴露,本键决定判了之后**对写做什么**。坏值**启动期拒**(`OFF`/`false`/`none` 这类「看起来像关掉」的写法一律不折成缺省 —— 静默回默认 = 一台自认为「暴露会话零收录」的部署其实在照常收录)。**部署席 only**(TaskSpec 无同名键,任务/受治脚本无法据此改姿态);**不冻进 checkpoint**(resume 腿跟当前部署配置走)。运维读面 = `GET /v1/diagnostics/wiring` 的 `memoryPosture.provenance`(`null`=本部署未设,引擎自缺省) |
|
|
202
203
|
|
|
203
204
|
⚠️ **拒启动**:多租户 + 记忆面点亮 + `projects[].defaultScopes` 里有 org 键 + 目录源缺席 ⇒ 启动报错
|
|
204
205
|
并点名 projectId(该部署的每个此类请求都会在 prepare 期整拒,响亮拒启动比静默全拒服务诚实)。
|
|
@@ -233,6 +234,14 @@ MODEL_CASCADE_LADDER=deepseek-flash,deepseek-pro # 目录里的模型名,cheap
|
|
|
233
234
|
- 数据驻留提示:`DB_BACKEND=local` 下显式 `SESSION_BACKEND=memory` 会被收编为 **durable(local)**
|
|
234
235
|
(1.292+ 裸 boot 默认 durable;/health 的 `sessionBackend` 报 `durable(local)`)——session 行落盘在
|
|
235
236
|
数据根下,清数据/隐私预期要按「sessions 在 engine-data 里」来做,不要按「只在内存」。
|
|
237
|
+
- **任务清单(TaskCreate/TaskList 家族)的持久边界**(#318):清单按**会话**分区,同一会话
|
|
238
|
+
跨 turn/跨 resume 连续,跨会话互不可见。配了 SQL 后端(`SESSION_BACKEND=mysql` / `DB_BACKEND=pg`)
|
|
239
|
+
时落 `task_list_meta`/`task_list_item` 两表 ⇒ **跨副本、跨重启都续得上**;**没有 SQL 后端的形态
|
|
240
|
+
(`local`/纯内存)清单只活在进程内 —— 重启即丢待办**(同时活跃会话超过 4096 时最老的会话清单被
|
|
241
|
+
LRU 回收)。要跨重启保住待办就配 SQL 后端;这与该形态下其余会话态设施(per-session cwd 等)是同一
|
|
242
|
+
条边界,不为它伪造持久性。**删除权**:`DELETE /v1/sessions/:id` 与托管留存 sweep 两条腿都把清单
|
|
243
|
+
行与编号高水位一并抹掉(同 anchors/附件/快照的级联)——会话 id 可被重新认领,残留就等于把上一位
|
|
244
|
+
主人的任务标题与编号进度交给下一个化身。
|
|
236
245
|
- 多实例边界:同一数据根同时只允许一个实例(BootLock 独占,第二个进程拒启;死主自愈含 SIGKILL)。
|
|
237
246
|
`DB_BACKEND=memory` 并非全无盘:workflow 相关账本仍挂数据根下——多个 memory 形引擎**不要共用**
|
|
238
247
|
数据根/HOME(1.309 起并发 boot 不再拒启,但账本内容级共享仍不受支持)。
|
|
@@ -138,7 +138,12 @@ export function createResolveSpec(ctx) {
|
|
|
138
138
|
const scenarioName = scenarios[requested] ? requested : "default";
|
|
139
139
|
// [891]→[2400]:"autonomous" alias 已退役(clay 裁不留过渡)——body 显式传它走 scenario_unknown 400。
|
|
140
140
|
// 定死 finalVerification:true 保存量行为)。warn 不 400——TB harness/存量部署平滑迁移。
|
|
141
|
-
|
|
141
|
+
// #318 第三参 = {@link ScenarioContext}(服务端事实,与不可信的 `body` 分开的通道)。`sessionId`
|
|
142
|
+
// 只能来自 `auth`(authorizer 解析 + 归属校验过的那一只)—— 与下游 assembleSpecLiteral 里
|
|
143
|
+
// 「sessionId comes from `auth`, NEVER the body」逐字同纪律:body.sessionId 是调用方自报串,
|
|
144
|
+
// 拿它当会话级设施(任务清单)的分区键就是让一个租户读另一个租户的清单。缺席(无 authorizer
|
|
145
|
+
// 的裸部署)⇒ 整键省略 ⇒ 场景侧退回 per-call 私店,不发明假 session。
|
|
146
|
+
const cap = selectScenario(scenarios, scenarioName)(body, auth?.principal, auth?.sessionId !== undefined ? { sessionId: auth.sessionId } : {});
|
|
142
147
|
// codex R3/R4 (appendSystemPrompt 案): ONE center-pack snapshot per request — the append-less gate below
|
|
143
148
|
// AND the provider pick (promptProvider field) both read THIS capture, never the mutable centerPrompts ref
|
|
144
149
|
// again (R4 TOCTOU: a refresh between the two reads could make the final provider assembled while the gate
|
|
@@ -19,6 +19,7 @@ import type { Logger } from "../observability/logger.js";
|
|
|
19
19
|
import type { Metrics } from "../observability/metrics.js";
|
|
20
20
|
import { WorkflowAgentRegistry } from "../orchestration/workflow-agent-steer.js";
|
|
21
21
|
import { type WorkflowCompletionInbox } from "../orchestration/workflow-completion-inbox.js";
|
|
22
|
+
import { type EngineNoticeRouter } from "../trace/engine-notice-wire.js";
|
|
22
23
|
import { WorkflowNotifyGate, type WorkflowCompletionPayload } from "../orchestration/workflow-notify-journal.js";
|
|
23
24
|
import type { ServiceWorkflowJournalStore, StoreBackend, ToolResultStoreFull } from "../plugins/store-backend.js";
|
|
24
25
|
import type { QuestionCoordinator } from "../question.js";
|
|
@@ -120,7 +121,7 @@ export interface RunnerDepsCtx {
|
|
|
120
121
|
}
|
|
121
122
|
/** design/158 A10 留档发现②:main runner `RunnerDeps` 与 main.ts subRunner 字面量之间此前手工重复
|
|
122
123
|
* 的 ~15 个键,类型标注见 {@link createSharedRunnerDeps} 头注。 */
|
|
123
|
-
export type SharedRunnerDeps = Pick<RunnerDeps, "brain" | "readFace" | "readDenyPatterns" | "readDenyBuiltinTiers" | "readDenyBuiltinExclude" | "memoryDelegationEvidence" | "models" | "roles" | "tiers" | "pricing" | "tracer" | "promptSource" | "executionEnvFactory" | "lspManager" | "backgroundAgentStore" | "mailboxStore" | "rosterStore" | "hooks" | "toolResultStore" | "hands" | "sessionPolicyStore" | "usageWindows" | "usageWindowStore" | "memoryScopeAdmission" | "deploymentMemoryScopes" | "sharedMemoryStores" | "compliancePostureResolver" | "lockedConfig" | "retentionPolicy" | "onNotice">;
|
|
124
|
+
export type SharedRunnerDeps = Pick<RunnerDeps, "brain" | "readFace" | "readDenyPatterns" | "readDenyBuiltinTiers" | "readDenyBuiltinExclude" | "memoryDelegationEvidence" | "memoryProvenance" | "models" | "roles" | "tiers" | "pricing" | "tracer" | "promptSource" | "executionEnvFactory" | "lspManager" | "backgroundAgentStore" | "mailboxStore" | "rosterStore" | "hooks" | "toolResultStore" | "hands" | "sessionPolicyStore" | "usageWindows" | "usageWindowStore" | "memoryScopeAdmission" | "deploymentMemoryScopes" | "sharedMemoryStores" | "compliancePostureResolver" | "lockedConfig" | "retentionPolicy" | "onNotice">;
|
|
124
125
|
/**
|
|
125
126
|
* design/158 A10 留档发现②(review 2026-07-29,[1543]§三族A 同源修补的延续):main runner 的
|
|
126
127
|
* `RunnerDeps` 字面量(下方 `createRunnerDeps`)与 `main.ts` 里 subRunner 的 `new Runner({...})`
|
|
@@ -163,10 +164,27 @@ export type SharedRunnerDepsCtx = Pick<RunnerDepsCtx, "config" | "brain" | "pric
|
|
|
163
164
|
* 🔴 `warn` 是**必填**(codex 复审第一轮,验真后修):可选链 `logger.warn?.(…)` 形在「只装了 `info` 的
|
|
164
165
|
* logger」上会把通告**吞掉** —— 比不接线还坏(不接线时 core 至少还打它自己的 `console.warn`)。所以
|
|
165
166
|
* 「有没有 warn」这个判断留在**装配点**:没有就别铸这一席,让 core 的响亮权留在原处。
|
|
167
|
+
*
|
|
168
|
+
* ## #310:双终点分流器(设计稿 = 黑板 [4630])
|
|
169
|
+
* 本体自 #310 起是**分流器**,两个终点:
|
|
170
|
+
* 1. **日志(全族,逐字保留)** —— 上面那段契约一个字节不变:任何 code、detail 原样、缺 detail 不铸键。
|
|
171
|
+
* 运维观察面零回退是本腿的硬前提(wire 半场是**增量**,不是搬家)。
|
|
172
|
+
* 2. **wire(白名单 + sessionId 路由)** —— `router.route(notice)`:码在
|
|
173
|
+
* {@link import("../trace/engine-notice-wire.js").ENGINE_NOTICE_WIRE_CODES} 内**且** `detail.sessionId`
|
|
174
|
+
* 在场时,投给那条会话的 run 腿(SSE + durable 账本);否则一帧不出。判据整条住在
|
|
175
|
+
* `src/trace/engine-notice-wire.ts`(白名单/归属/消毒/隔离四件同源),这里只负责**先日志后分流**的次序。
|
|
176
|
+
*
|
|
177
|
+
* 🔴 次序是承重的:日志在前 ⇒ 即便 wire 半场整条坏掉(路由抛、口抛),事实仍然完整落在运维面。
|
|
178
|
+
* 反过来写就是「wire 故障吃掉运维日志」,那正是本仓 #157 纪律要根除的形。route 自身按 sink 隔离 +
|
|
179
|
+
* F 类留痕(`server.engine-notice.wire-sink-threw`),不向 core 回抛。
|
|
180
|
+
*
|
|
181
|
+
* 🔴 `router` **有默认值**(进程单例)而不是必填:这一席有五个以上装配点(共享基座 / leader 车道四处 /
|
|
182
|
+
* 未来任何新车道),必填会让"新车道忘了传 router"变成**静默半聋**(日志有、wire 无)——与本函数当初
|
|
183
|
+
* 被抽成具名导出的理由逐字同一个。测试要隔离时显式传自己 new 的实例。
|
|
166
184
|
*/
|
|
167
185
|
export declare function createEngineNoticeForwarder(logger: {
|
|
168
186
|
warn: (msg: string, fields?: Record<string, unknown>) => void;
|
|
169
|
-
}): NonNullable<RunnerDeps["onNotice"]>;
|
|
187
|
+
}, router?: EngineNoticeRouter): NonNullable<RunnerDeps["onNotice"]>;
|
|
170
188
|
/**
|
|
171
189
|
* `onNotice` **席**的装配判据本身 —— 「`warn` 真在场才铸席」这一条从此是**一个可直接调用的函数**,
|
|
172
190
|
* 不是散在装配点、只能靠扫源码文本去猜的一行三元(codex 对抗复审 R1-[medium],验真后修)。
|
|
@@ -186,7 +204,7 @@ export declare function createEngineNoticeForwarder(logger: {
|
|
|
186
204
|
*/
|
|
187
205
|
export declare function createEngineNoticeSeat(logger: {
|
|
188
206
|
warn?: (msg: string, fields?: Record<string, unknown>) => void;
|
|
189
|
-
} | undefined): {
|
|
207
|
+
} | undefined, router?: EngineNoticeRouter): {
|
|
190
208
|
onNotice?: NonNullable<RunnerDeps["onNotice"]>;
|
|
191
209
|
};
|
|
192
210
|
export declare function createSharedRunnerDeps(ctx: SharedRunnerDepsCtx): SharedRunnerDeps;
|
package/dist/boot/runner-deps.js
CHANGED
|
@@ -17,6 +17,7 @@ import { createHardenedVmRunner } from "../orchestration/hardened-vm-runner.js";
|
|
|
17
17
|
import { createWorkerHardenedVmRunner } from "../orchestration/hardened-vm-worker-runner.js";
|
|
18
18
|
import { WorkflowAgentRegistry } from "../orchestration/workflow-agent-steer.js";
|
|
19
19
|
import { resolveServedSession } from "../orchestration/workflow-completion-inbox.js";
|
|
20
|
+
import { defaultEngineNoticeRouter } from "../trace/engine-notice-wire.js"; // #310:通告分流器的 wire 终点
|
|
20
21
|
import { WorkflowNotifyGate } from "../orchestration/workflow-notify-journal.js";
|
|
21
22
|
import { makeLoadProjectMemory, makeProbeInstructionSources } from "../project-memory.js";
|
|
22
23
|
import { workflowModelAllowlistFor } from "../task-workflow.js";
|
|
@@ -38,9 +39,29 @@ export function createRunnerDepsOnAsk(toolApproval) {
|
|
|
38
39
|
* 🔴 `warn` 是**必填**(codex 复审第一轮,验真后修):可选链 `logger.warn?.(…)` 形在「只装了 `info` 的
|
|
39
40
|
* logger」上会把通告**吞掉** —— 比不接线还坏(不接线时 core 至少还打它自己的 `console.warn`)。所以
|
|
40
41
|
* 「有没有 warn」这个判断留在**装配点**:没有就别铸这一席,让 core 的响亮权留在原处。
|
|
42
|
+
*
|
|
43
|
+
* ## #310:双终点分流器(设计稿 = 黑板 [4630])
|
|
44
|
+
* 本体自 #310 起是**分流器**,两个终点:
|
|
45
|
+
* 1. **日志(全族,逐字保留)** —— 上面那段契约一个字节不变:任何 code、detail 原样、缺 detail 不铸键。
|
|
46
|
+
* 运维观察面零回退是本腿的硬前提(wire 半场是**增量**,不是搬家)。
|
|
47
|
+
* 2. **wire(白名单 + sessionId 路由)** —— `router.route(notice)`:码在
|
|
48
|
+
* {@link import("../trace/engine-notice-wire.js").ENGINE_NOTICE_WIRE_CODES} 内**且** `detail.sessionId`
|
|
49
|
+
* 在场时,投给那条会话的 run 腿(SSE + durable 账本);否则一帧不出。判据整条住在
|
|
50
|
+
* `src/trace/engine-notice-wire.ts`(白名单/归属/消毒/隔离四件同源),这里只负责**先日志后分流**的次序。
|
|
51
|
+
*
|
|
52
|
+
* 🔴 次序是承重的:日志在前 ⇒ 即便 wire 半场整条坏掉(路由抛、口抛),事实仍然完整落在运维面。
|
|
53
|
+
* 反过来写就是「wire 故障吃掉运维日志」,那正是本仓 #157 纪律要根除的形。route 自身按 sink 隔离 +
|
|
54
|
+
* F 类留痕(`server.engine-notice.wire-sink-threw`),不向 core 回抛。
|
|
55
|
+
*
|
|
56
|
+
* 🔴 `router` **有默认值**(进程单例)而不是必填:这一席有五个以上装配点(共享基座 / leader 车道四处 /
|
|
57
|
+
* 未来任何新车道),必填会让"新车道忘了传 router"变成**静默半聋**(日志有、wire 无)——与本函数当初
|
|
58
|
+
* 被抽成具名导出的理由逐字同一个。测试要隔离时显式传自己 new 的实例。
|
|
41
59
|
*/
|
|
42
|
-
export function createEngineNoticeForwarder(logger) {
|
|
43
|
-
return (notice) =>
|
|
60
|
+
export function createEngineNoticeForwarder(logger, router = defaultEngineNoticeRouter) {
|
|
61
|
+
return (notice) => {
|
|
62
|
+
logger.warn("engine_notice", { code: notice.code, message: notice.message, ...(notice.detail !== undefined ? { detail: notice.detail } : {}) });
|
|
63
|
+
router.route(notice);
|
|
64
|
+
};
|
|
44
65
|
}
|
|
45
66
|
/**
|
|
46
67
|
* `onNotice` **席**的装配判据本身 —— 「`warn` 真在场才铸席」这一条从此是**一个可直接调用的函数**,
|
|
@@ -59,9 +80,9 @@ export function createEngineNoticeForwarder(logger) {
|
|
|
59
80
|
* 与键缺席在 core 那侧行为逐字相同、响亮权都留在 core——旧句「显式 undefined 也算 host 接管」与真码相反):
|
|
60
81
|
* 展开形让 `new Runner({...seat})` 的键集只随真装配变化,EXPECTED_SHARED_KEYS 类的键集绊线才数得准。
|
|
61
82
|
*/
|
|
62
|
-
export function createEngineNoticeSeat(logger) {
|
|
83
|
+
export function createEngineNoticeSeat(logger, router = defaultEngineNoticeRouter) {
|
|
63
84
|
const warn = logger?.warn?.bind(logger);
|
|
64
|
-
return warn ? { onNotice: createEngineNoticeForwarder({ warn }) } : {};
|
|
85
|
+
return warn ? { onNotice: createEngineNoticeForwarder({ warn }, router) } : {};
|
|
65
86
|
}
|
|
66
87
|
// #289(core 5.43.0 #316 观测席,[4448] 表态② 的评估结论,如实登记):`onSecretEnvScrub` 席在 server
|
|
67
88
|
// 侧**无真挂点**——host lane 的执行 env(真跑用户 Bash 的那只)由 core 引擎自建,server 构造的三只
|
|
@@ -114,6 +135,12 @@ export function createSharedRunnerDeps(ctx) {
|
|
|
114
135
|
// 铸点要求污染面在场)。边界钉 = `test/memory-delegation-evidence-lane.test.ts` 的 ⑤ 组。
|
|
115
136
|
// 缺席=undefined=core 自缺省(`static-face`)。
|
|
116
137
|
memoryDelegationEvidence: ctx.config.memoryDelegationEvidence ?? undefined,
|
|
138
|
+
// #307 件4(core 5.46.0 design/336 §13-3):provenance 总开关 —— 与上一席**同族同姿态**的部署座席。
|
|
139
|
+
// 同样必须三族(主 runner / subRunner / run-local)同源:core「每个 run 读准备它的那只 Runner 的
|
|
140
|
+
// deps」⇒ 漏挂 subRunner = 委派子代按另一套 provenance 写记忆(部署声称「暴露会话零收录」而子代
|
|
141
|
+
// 照收)。leader lane 的族级缺口同上一席,边界钉在 `memory-delegation-evidence-lane.test.ts` ⑤ 组
|
|
142
|
+
// 的 DEPLOYMENT_SEATS 名单里(本席已入册)。缺席=undefined=core 自缺省(`carry`)。
|
|
143
|
+
memoryProvenance: ctx.config.memoryProvenance ?? undefined,
|
|
117
144
|
executionEnvFactory: ctx.executionEnvFactory ? ctx.executionEnvFactory : undefined,
|
|
118
145
|
lspManager: ctx.lspManager ? ctx.lspManager : undefined,
|
|
119
146
|
// core 1.364 durable bg agents 读半场(写半场=scenarioDeps.backgroundAgentStore 同实例,组装区注释)。
|
|
@@ -7,6 +7,7 @@ import type { CheckpointStoreFull, StoreBackend, ToolResultStoreFull } from "../
|
|
|
7
7
|
import type { TaskAttachmentStore } from "../plugins/task-attachment-store.js";
|
|
8
8
|
import type { OwnerAwareSessionStore } from "../security.js";
|
|
9
9
|
import { SessionWatchRegistry } from "../session-watch.js";
|
|
10
|
+
import type { TaskListLane } from "./task-list-lane.js";
|
|
10
11
|
export interface SessionFacesCtx {
|
|
11
12
|
config: ServiceConfig;
|
|
12
13
|
logger: Logger;
|
|
@@ -23,6 +24,8 @@ export interface SessionFacesCtx {
|
|
|
23
24
|
taskAttachmentStore: TaskAttachmentStore | undefined;
|
|
24
25
|
fileSnapshotStore: ReturnType<StoreBackend["fileSnapshot"]> | undefined;
|
|
25
26
|
workflowCompletionInbox: WorkflowCompletionInbox | undefined;
|
|
27
|
+
/** #318:会话级任务清单车道(E21 级联的一条腿)。缺席 = 装配未接本车道 ⇒ 无清单可删。 */
|
|
28
|
+
taskListLane: TaskListLane | undefined;
|
|
26
29
|
}
|
|
27
30
|
export declare function createSessionFaces(ctx: SessionFacesCtx): {
|
|
28
31
|
ownerAware: OwnerAwareSessionStore;
|
|
@@ -18,7 +18,7 @@ import { setLeafAdvanceListener } from "../session-leaf-bus.js";
|
|
|
18
18
|
import { SessionWatchRegistry, posIntEnv } from "../session-watch.js";
|
|
19
19
|
import { defaultTaskRegistry } from "@sema-agent/core";
|
|
20
20
|
export function createSessionFaces(ctx) {
|
|
21
|
-
const { config, logger, metrics, localRoot, backend, sessionStore, runStore, checkpointStore, toolResultStore, resumeAnchorStore, approvalExemptionStore, sessionPolicyStore, taskAttachmentStore, fileSnapshotStore, workflowCompletionInbox, } = ctx;
|
|
21
|
+
const { config, logger, metrics, localRoot, backend, sessionStore, runStore, checkpointStore, toolResultStore, resumeAnchorStore, approvalExemptionStore, sessionPolicyStore, taskAttachmentStore, fileSnapshotStore, workflowCompletionInbox, taskListLane, } = ctx;
|
|
22
22
|
// Audit回溯 + degenerate-output instrument are TiDB-specific raw-SQL consumers (outside the store
|
|
23
23
|
// abstraction); they run only on the TiDB backend (the mysql2 pool), and are a no-op on PG until ported.
|
|
24
24
|
const mysqlPool = backend?.mysqlPool();
|
|
@@ -99,7 +99,8 @@ export function createSessionFaces(ctx) {
|
|
|
99
99
|
}
|
|
100
100
|
// 📌 **残余,如实登记**(codex R2 判真但**射程外**,不在本批修):本围栏钉住的是「purge 开始那一刻
|
|
101
101
|
// 化身是对的」+ run 腿的事务内原子复核。其余按 session_id 单键删的腿(anchors / exemption / policy /
|
|
102
|
-
// attachment / snapshot / tool_result 的 provenance 形)
|
|
102
|
+
// attachment / snapshot / task_list(#318 新入册,codex R2-[high] 同判)/ tool_result 的 provenance 形)
|
|
103
|
+
// 在**本批之前就是**这个 scoping(理由逐字见下方
|
|
103
104
|
// anchors 腿注,可追到公开快照那一版),它们各自开始的时刻理论上仍可能落在一次化身漂移之后。
|
|
104
105
|
// 要真正消灭它得让**整条**级联化身原子(SQL 单事务横跨十余张表 + 对象存储,即留存腿那个形),而在线
|
|
105
106
|
// 删除面刻意不是那个形:它要跨事务崩溃收敛(meta 最后删 ⇒ 幂等重试),还要动 MinIO 字节。那是设计级
|
|
@@ -157,6 +158,14 @@ export function createSessionFaces(ctx) {
|
|
|
157
158
|
// D-1 附件随会话删(E21 级联;session_id 单键 scoping,与 anchors 同理由——路由已证会话所有权)。
|
|
158
159
|
if (taskAttachmentStore)
|
|
159
160
|
await taskAttachmentStore.deleteBySession(sessionId);
|
|
161
|
+
// #318 任务清单(TaskCreate/TaskList 家族)是 per-session 的用户内容(任务标题/描述/元数据)
|
|
162
|
+
// → 随会话删,scoping 与 anchors 同理由(路由已在 DELETE 门证过会话所有权)。
|
|
163
|
+
// 🔴 **不是可选的卫生腿**:会话 id 由调用方自选、删除后可被别人 `register` 重新登记,而车道的
|
|
164
|
+
// listKey 是 sessionId 的确定性派生 ⇒ 不删的话新化身第一次 TaskList 就读到上一位主人的任务,
|
|
165
|
+
// TaskCreate 还从上一位的高水位续号(codex R1-[high] 验真后接)。错误如实上抛 = 与同族腿一致
|
|
166
|
+
// (子腿失败 ⇒ 不删 meta ⇒ 幂等重试收敛)。
|
|
167
|
+
if (taskListLane)
|
|
168
|
+
await taskListLane.deleteBySession(sessionId);
|
|
160
169
|
// E19 rewind-files snapshots are per-session working-tree state → purge them too (scoped by sessionId; the
|
|
161
170
|
// durable twins carry deleteBySession, local InMemory omits it → optional no-op on local).
|
|
162
171
|
if (fileSnapshotStore?.deleteBySession)
|
|
@@ -2,38 +2,43 @@
|
|
|
2
2
|
* #303([4610] test 报「/model 中途切换校验探针经 POST /v1/side-query 对有效 key 返回上游 401」)——
|
|
3
3
|
* side-query 面的 **per-model key plane** 装配。
|
|
4
4
|
*
|
|
5
|
-
* ## 病灶(
|
|
5
|
+
* ## 病灶(#303 立案时亲验 core 5.43.0 dist 实现行)
|
|
6
6
|
*
|
|
7
|
-
* core 的 side-query verb
|
|
7
|
+
* core 的 side-query verb 当时**结构性没有 key 座位**:
|
|
8
8
|
* · `core/side-query.d.ts` 的 `SideQuerySpec` 无 `apiKey` / `getApiKeyAndHeaders` 字段;
|
|
9
9
|
* · `SideQueryDeps` = `{ brain, models?, roles? }`;
|
|
10
10
|
* · `core/side-query.js` 铸 brain `options` 时只放 `signal/maxTokens/reasoning`,**不含 apiKey**。
|
|
11
|
-
* 而 openai brain 的 `buildRequest`(`brain/openai.js
|
|
11
|
+
* 而 openai brain 的 `buildRequest`(`brain/openai.js`)是这两行:
|
|
12
12
|
* `const apiKey = options?.apiKey ?? config.apiKey;` ← 缺席即回落 **brain 构造时的网关 key**
|
|
13
13
|
* `const root = (model.baseUrl || config.baseUrl || "")` ← 却**按模型**选路
|
|
14
14
|
* ⇒ 对带 per-model `baseUrl` + `apiKeyEnv`/`sealedApiKey` 的模型,side-query 把**主网关 key 发到
|
|
15
15
|
* 外部模型 URL**:用户看到上游 401,同时这是一次凭据外泄(安全轴)。
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* side-query
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* `
|
|
16
|
+
* 当时的窄修 = 一只 **key 注入 brain wrapper**(`createPerModelKeyBrain`),并在顶注写明「core 一旦
|
|
17
|
+
* 给 `SideQuerySpec` 补上 key 座位,本 wrapper 即可撤」,撤除信号挂机器钉。
|
|
18
|
+
*
|
|
19
|
+
* ## #341 换装(core 5.46.0 提货,[4615] 座位到货 —— 撤除条件已满足)
|
|
20
|
+
*
|
|
21
|
+
* core 5.46.0 的 `SideQuerySpec` 有了 `getApiKeyAndHeaders?: TaskSpec["getApiKeyAndHeaders"]`,
|
|
22
|
+
* `runSideQuery` 每次调用按**解析后的** `Model` 调它,并把 `{apiKey, headers}` 铸进 brain options
|
|
23
|
+
* (`dist/core/side-query.js`:`const auth = await spec.getApiKeyAndHeaders?.(resolved.model);`)——
|
|
24
|
+
* 与主推理链逐字同一只座位形。于是 side-query 席**撤掉 brain wrapper**,key 供给改走 spec 座位:
|
|
25
|
+
* · 本席不再在 side-query 路上包 `ctx.brain`,`RunnerDeps.brain` 的零扰动从「wrapper 只包一只引用」
|
|
26
|
+
* 降级成**结构性事实**(这条路上根本没有 wrapper);
|
|
27
|
+
* · `headers` 半边从此有出路(wrapper 形只注得进 `apiKey`,core 的座位 `{apiKey, headers?}` 两半都收);
|
|
28
|
+
* · 判据(`gatewayBaseUrl`)与 resolver 一样**按调用现取** —— 见下面 `createSideQueryLane` 的注。
|
|
29
|
+
* `createPerModelKeyBrain` **不删**:它仍是 WebFetch 摘要面(`boot/webfetch-summarize-lane.ts`)的
|
|
30
|
+
* key plane —— core 的 `WebFetchSummarizerOptions` 至今只有 `maxContentChars`,那一面没有座位可换
|
|
31
|
+
* (亲验 core 5.46.0 `dist/tools/web.d.ts`)。
|
|
28
32
|
*
|
|
29
33
|
* 🔴 **poison(sealed key 不可解)上抛,绝不静默回落网关 key** —— `resolveModelApiKey` 抛
|
|
30
34
|
* `SealedKeyPoisonedError` 是「这个模型配了托管密钥但解不开」的响亮态;吞成 undefined 等于用共享
|
|
31
|
-
* 网关账号去打这只模型的上游,正是毒丸机制存在的理由(CLAUDE.md #157 安全轴 fail-closed)
|
|
35
|
+
* 网关账号去打这只模型的上游,正是毒丸机制存在的理由(CLAUDE.md #157 安全轴 fail-closed)。换装后
|
|
36
|
+
* 这条语义由 core 承载得更彻底:座位抛 ⇒ `runSideQuery` 的 `await` 直接上抛,brain 一个字节都没发。
|
|
32
37
|
*
|
|
33
38
|
* ## 具名残余:**无 per-model key 的 off-route 模型仍收网关 key**(codex R1-F1,逐条评估后如实留)
|
|
34
39
|
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
40
|
+
* 判据本身没变(座位到货也没改它):`undefined` ⇒ 座位不给凭据 ⇒ core 用网关 key,即使这只模型的
|
|
41
|
+
* `baseUrl` 指向别家。为什么**不**在本批把它改成 fail-closed(逐条,而不是"先不管"):
|
|
37
42
|
* ① **与主推理链同语义**才是当前的正确性基准 —— 任务面(core 每次调 `getApiKeyAndHeaders`,返回
|
|
38
43
|
* `undefined` 即网关 key)在**同一个** catalog、**同一批**模型上就是这个姿势。只把 side-query 收严,
|
|
39
44
|
* 等于让同一只模型「跑任务能用、问一句就 401」,而任务面才是量大的那一面 —— 半边收严的覆盖比
|
|
@@ -46,14 +51,6 @@
|
|
|
46
51
|
* ⇒ 本批**不改行为**,把它作为**跨面语义问题**上报(要收严就三面一起收严、带旋钮、走表态制),
|
|
47
52
|
* 而不是在这一面偷偷收严。真要收严时的正解:按 `normUrl` 比对 `gatewayBaseUrl`(+fallback 列表)
|
|
48
53
|
* 与 anthropic 路由,off-route ∧ 无 per-model key ⇒ 拒发,并同批改任务面。
|
|
49
|
-
*
|
|
50
|
-
* ## 撤除条件(设计成可撤形)
|
|
51
|
-
*
|
|
52
|
-
* core 一旦给 `SideQuerySpec` 补上 key 座位(`apiKey` 或 `getApiKeyAndHeaders`),本模块的 wrapper
|
|
53
|
-
* 即可撤:装配点改成把 `getKeyResolver()` 直接塞进 spec,`createPerModelKeyBrain` 整只删除。
|
|
54
|
-
* 撤除信号有机器钉:`test/side-query-key-plane.test.ts` 的「core 座位缺席钉」在 core 补座位那天先红。
|
|
55
|
-
* 在那之前,本模块**不是** upstream 语义的旁路重写 —— 它只在 server 自己的装配层给自己的 brain
|
|
56
|
-
* 引用补上 server 自己的凭据平面,core 的 side-query 语义(选路/归一/结果形)原样由 `runSideQuery` 决定。
|
|
57
54
|
*/
|
|
58
55
|
import { type Brain, type SideQueryResult, type SideQuerySpec } from "@sema-agent/core";
|
|
59
56
|
import type { ServiceConfig } from "../config-types.js";
|
|
@@ -62,6 +59,8 @@ import type { ModelKeyRef } from "../key-resolver.js";
|
|
|
62
59
|
export type KeyResolver = ((model: ModelKeyRef) => Promise<{
|
|
63
60
|
apiKey: string;
|
|
64
61
|
} | undefined>) | undefined;
|
|
62
|
+
/** core 5.46.0 起 side-query 与任务面共用的 per-model auth 座(`SideQuerySpec.getApiKeyAndHeaders`)。 */
|
|
63
|
+
export type PerModelAuthSeat = NonNullable<SideQuerySpec["getApiKeyAndHeaders"]>;
|
|
65
64
|
/** side-query 执行席:HTTP 路由(`routes/side-query.ts`)唯一的 brain 出口。 */
|
|
66
65
|
export type SideQueryLane = (spec: SideQuerySpec) => Promise<SideQueryResult>;
|
|
67
66
|
/**
|
|
@@ -71,54 +70,78 @@ export type SideQueryLane = (spec: SideQuerySpec) => Promise<SideQueryResult>;
|
|
|
71
70
|
*
|
|
72
71
|
* 参照系缺席(`gatewayBaseUrl` 未给)⇒ 回 `false`:判不了就不判,别猜。
|
|
73
72
|
*
|
|
74
|
-
* ⚠️ `hooks/hook-llm.ts
|
|
73
|
+
* ⚠️ `hooks/hook-llm.ts` 有一只逐字同形的内联归一器(它用同一判据做 **fail-closed 拒发**,本函数只
|
|
75
74
|
* 做**计数**)。两处合一属跨面收严件 #309 的射程(那时三面同批换判据),本批刻意不动 hook 面的 fail-closed
|
|
76
75
|
* 门 —— 在只改可观测性的批里重写另一条安全轴的门,风险与收益不对等。
|
|
77
76
|
*/
|
|
78
77
|
export declare function isOffRouteBaseUrl(modelBaseUrl: string | undefined, gatewayBaseUrl: string | undefined): boolean;
|
|
79
78
|
export interface SideQueryLaneCtx {
|
|
80
|
-
/** 与 `RunnerDeps.brain` **同一只**实例(
|
|
79
|
+
/** 与 `RunnerDeps.brain` **同一只**实例(#341 换装后本席不再包它 —— key 走 spec 座位)。 */
|
|
81
80
|
brain: Brain;
|
|
82
|
-
/**
|
|
81
|
+
/** 活配置引用:目录/角色表/网关地址被 config-center 就地热应用(`mutateInPlace`),按调用取值才跟得上。 */
|
|
83
82
|
config: ServiceConfig;
|
|
84
83
|
/** 活引用取值 —— registry 热应用整个换 resolver 引用(`boot/config-center.ts` 每次 apply 重铸)。 */
|
|
85
84
|
getKeyResolver: () => KeyResolver;
|
|
86
85
|
}
|
|
87
86
|
/**
|
|
88
|
-
* per-model
|
|
87
|
+
* per-model auth 座(#341,core 5.46.0 `SideQuerySpec.getApiKeyAndHeaders` 的 server 侧填充物)。
|
|
88
|
+
*
|
|
89
|
+
* 语义(逐条对齐主推理链 `getApiKeyAndHeaders`,也逐条对齐它取代的 wrapper 那三条臂):
|
|
90
|
+
* · `upstream`(spec 自带的座位)**解出凭据** ⇒ 原样透传,含 `headers`(上游/装饰器已经定了凭据,
|
|
91
|
+
* 本层不抢 —— wrapper 时代的「`options.apiKey` 已在场」臂);判别按**每次调用**的返回值,不是
|
|
92
|
+
* 「座位在场就整只让位」:上游对这只模型没意见时,本席照常按模型解 key;
|
|
93
|
+
* · resolver 缺席(部署根本没配 per-model key)⇒ 回 `undefined` ⇒ core 用网关 key(今日行为逐字不变);
|
|
94
|
+
* · resolver 返回 undefined(这只模型没有自己的 key)⇒ 回 `undefined` ⇒ 网关 key(additive 契约);
|
|
95
|
+
* · resolver 抛(sealed key 中毒)⇒ **上抛**,`runSideQuery` 的 `await` 直接把它扔给调用方,
|
|
96
|
+
* 本次调用整体失败,一个字节都不发。
|
|
97
|
+
*
|
|
98
|
+
* 🔴 A-057.59 [PARTIAL high](2026-08-19;#341 换装后**处置不变**)—— 「无 per-model key 的 off-route
|
|
99
|
+
* 模型仍收网关 key」这条具名残余**行为不变**(逐条理由见模块顶注「具名残余」段;core 的座位是
|
|
100
|
+
* 「absent ⇒ construction-time credentials apply」,与主推理链
|
|
101
|
+
* `dist/engine/harness/agent-harness.js` 的 `auth?.apiKey !== undefined` 臂逐字同语义,座位到货并没有
|
|
102
|
+
* 改变这条残余,所以 `P-DEBT` 计数**不可销**),但**每次调用零留痕**这一半是要清偿的存量欠账:
|
|
103
|
+
* config-apply 期那条 `sema_registry_model_key_env_missing` warn 只覆盖「声明了 env 名却没设」,覆盖不到
|
|
104
|
+
* 「压根没配 per-model key 的 off-route 模型」,而后者才是真发生外泄的那一形。按 CLAUDE.md #157
|
|
105
|
+
* 「一时改不动的 P 类记 `P-DEBT`」补计数:命中条件 = **解不出 per-model key ∧ 该模型的 baseUrl 不是本
|
|
106
|
+
* 部署网关** ⇒ 这一次调用真把网关凭据发给了别家主机。`gatewayBaseUrl` 缺席(调用方没给参照系)⇒ 不计
|
|
107
|
+
* (不猜),与「宁可漏计不可错计」同向。
|
|
108
|
+
*/
|
|
109
|
+
export declare function createPerModelAuthSeat(getKeyResolver: () => KeyResolver, opts?: {
|
|
110
|
+
readonly gatewayBaseUrl?: string;
|
|
111
|
+
readonly upstream?: SideQuerySpec["getApiKeyAndHeaders"];
|
|
112
|
+
}): PerModelAuthSeat;
|
|
113
|
+
/**
|
|
114
|
+
* per-model key 注入 brain wrapper —— **#341 后只服务 WebFetch 摘要面**
|
|
115
|
+
* (`boot/webfetch-summarize-lane.ts`)。side-query 面已换到 core 的 spec 座位(见顶注「#341 换装」);
|
|
116
|
+
* core 的 `WebFetchSummarizerOptions` 至今只有 `maxContentChars`(亲验 5.46.0 `dist/tools/web.d.ts`),
|
|
117
|
+
* 那一面**没有座位可换**,只能继续在 brain 层补凭据。
|
|
89
118
|
*
|
|
90
|
-
* 语义(
|
|
119
|
+
* 语义(与 `createPerModelAuthSeat` 同一张表,只是注入位在 `options.apiKey`):
|
|
91
120
|
* · `options.apiKey` **已在场** ⇒ 原样透传(上游/装饰器已经定了凭据,本层不抢);
|
|
92
|
-
* · resolver 缺席
|
|
93
|
-
* · resolver 返回 undefined(这只模型没有自己的 key)⇒ 不注入 ⇒ 网关 key(additive 契约);
|
|
121
|
+
* · resolver 缺席 / 返回 undefined ⇒ 不注入 ⇒ 网关 key(additive 契约);
|
|
94
122
|
* · resolver 抛(sealed key 中毒)⇒ **上抛**,本次调用整体失败,一个字节都不发。
|
|
95
123
|
*
|
|
96
|
-
* `complete` 在场时同样包一层:core 的 `Brain.complete` 是 compaction
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
* 🔴 A-057.59 [PARTIAL high](2026-08-19)—— 「无 per-model key 的 off-route 模型仍收网关 key」这条
|
|
100
|
-
* 具名残余**行为不变**(逐条理由见模块顶注「具名残余」段:与主推理链
|
|
101
|
-
* `dist/engine/harness/agent-harness.js` 的 `auth?.apiKey !== undefined` 臂逐字同语义,单面收严会造成
|
|
102
|
-
* 「同一模型跑任务能用、问一句 401」;收严件 = 跨面 #309,带旋钮走表态制),但**每次调用零留痕**这一半
|
|
103
|
-
* 被判为要清偿的存量欠账:config-apply 期那条 `sema_registry_model_key_env_missing` warn 只覆盖
|
|
104
|
-
* 「声明了 env 名却没设」,覆盖不到「压根没配 per-model key 的 off-route 模型」,而后者才是真发生外泄
|
|
105
|
-
* 的那一形。按 CLAUDE.md #157「一时改不动的 P 类记 `P-DEBT`」补计数:命中条件 =
|
|
106
|
-
* **解不出 per-model key ∧ 该模型的 baseUrl 不是本部署网关** ⇒ 这一次调用真把网关凭据发给了别家主机。
|
|
107
|
-
* `gatewayBaseUrl` 缺席(调用方没给参照系)⇒ 不计(不猜),与「宁可漏计不可错计」同向。
|
|
124
|
+
* `complete` 在场时同样包一层:core 的 `Brain.complete` 是 compaction/摘要腿的直调口(WebFetch 摘要器
|
|
125
|
+
* 正是**优先**走它),漏包等于给同一个病留第二条路。
|
|
108
126
|
*/
|
|
109
127
|
export declare function createPerModelKeyBrain(brain: Brain, getKeyResolver: () => KeyResolver, opts?: {
|
|
110
128
|
readonly gatewayBaseUrl?: string;
|
|
111
129
|
}): Brain;
|
|
112
130
|
/**
|
|
113
|
-
* 装配 side-query 执行席 = 真 `runSideQuery` +
|
|
114
|
-
*
|
|
115
|
-
* `models`
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
131
|
+
* 装配 side-query 执行席 = 真 `runSideQuery` + core 的 per-model auth 座,models/roles 与 Runner 同源。
|
|
132
|
+
*
|
|
133
|
+
* **按调用**取三件活值(`models` / `getKeyResolver` / `gatewayBaseUrl`),因为它们是**同一次** center
|
|
134
|
+
* 下发里成对换代的(#303 codex R1-F2:目录与密钥表零 await 窗成对换代):
|
|
135
|
+
* · `models` = `expandTiers(config.models, config.tiers)` —— 档位词 / CC 别名(`flash`/`opus`/…)在
|
|
136
|
+
* side-query 上照常可选路(core 的 `Runner` 构造时做同一件事),且 `Runner` 那次展开是**构造时快照**,
|
|
137
|
+
* 而本路由的模型白名单门(`routes/side-query.ts` 的 `isModelAllowlisted`)按请求现算同一张增广目录 ——
|
|
138
|
+
* 两边取同一份才不会出现「名单放行、选路说不认识」的分歧;
|
|
139
|
+
* · `getKeyResolver()` —— 热应用整个换 resolver 引用,活取才跟得上(装配时快照 = 旧 key 用到重启);
|
|
140
|
+
* · `config.gatewayBaseUrl` —— off-route 判据的参照系。装配时快照会让「换网关那一刻起,这次调用把网关
|
|
141
|
+
* 凭据发给了别家」在遥测里永久失明(钉:`test/side-query-key-plane.test.ts` 的活取钉)。
|
|
142
|
+
*
|
|
143
|
+
* `spec` 自带的 `getApiKeyAndHeaders`(嵌入方/装饰器)**不被覆盖** —— 它作为 `upstream` 折进本席,
|
|
144
|
+
* 每次调用先问它,它没意见时才由本席按模型解(见 `createPerModelAuthSeat` 顶注第一条臂)。
|
|
122
145
|
*/
|
|
123
146
|
export declare function createSideQueryLane(ctx: SideQueryLaneCtx): SideQueryLane;
|
|
124
147
|
//# sourceMappingURL=side-query-lane.d.ts.map
|