发现于 #5129(E5b 端点链接线)的实现过程中,**不是本单引入的缺陷**,也不在本单范围内 —— 如实记录,交 PM 分诊。 ## 事实 `packages/runtime/src/http-dispatcher.ts`:每个 host 只构造**一个** `HttpDispatcher`(`dispatcher-plugin.ts` 的 `start()` 里 `new HttpDispatcher(kernel, …)`,所有路由共享它),但「本请求解析到哪个 kernel」被写在**实例字段** `this.kernel` 上: ``` this.kernel = (await this.kernelResolver.resolveKernel(context, this.defaultKernel)) ?? this.defaultKernel; ``` 而 `resolveService()` / `getObjectQLService()` / `domainDeps.getRequestKernelService()` 全部从 `this.kernel` 读。域处理器在这一次赋值之后会跨多个 `await` 边界继续读它。 于是在**多租户 host**(注册了 `kernel-resolver`)上,两个交错的请求 A(env-1)、B(env-2)可以是: 1. A 解析 → `this.kernel = kernelOfEnv1`; 2. A 在某个 `await`(会话解析、driver 查询)让出; 3. B 解析 → `this.kernel = kernelOfEnv2`; 4. A 恢复,`resolveService('objectql')` 读到的是 **env-2 的 kernel**。 Node 单线程不保护这个字段 —— 保护的前提是「不跨 await 持有可变共享状态」,而这里恰好跨了。 ## 为什么今天不一定看得见 单 kernel 部署里 `this.kernel === this.defaultKernel` 恒成立,赋值是幂等的,所以本地与 CI 都不会暴露。只有注册了 `kernel-resolver` 的多租户分发(`cloud`)才有第二个取值,而那条路径没有并发交错的回归测试。 **我没有复现**,只有代码事实。严重度请分诊时判断:如果可达,它的形状是「一个租户的请求读到另一个租户的数据源」,不是性能问题。 ## 与 #5129 的关系 E5b 把声明式端点步接到了同一个解析上(`HttpDispatcher.resolveRequestScope`,由 `dispatch()` 里原地抽出,行为逐字节不变),所以这个字段**多了一个写入方**。#5129 没有加重也没有减轻这个类:同样的赋值,同样的读法。之所以在这里记而不是在那条 PR 里顺手改,是因为修法(把 per-request 状态从实例字段挪到显式参数 / `AsyncLocalStorage`)会穿过 `domainDeps` 的每一个消费者,那是一单独立改动,不该混进一次接线单。 ## 可能的修法(留给分诊,不预设结论) - A:把 kernel 作为参数逐层传进域处理器(改 `DomainHandlerDeps` 契约,面大但显式); - B:`AsyncLocalStorage` 承载 per-request kernel(改动小,但引入隐式上下文); - C:每请求 new 一个轻量 dispatcher facade(分配成本 vs. 正确性)。 先确认可达性(写一个多租户交错的回归测试)再选,比先选修法更省。
Activity
baozhoutao commentedon Aug 5, 2026
🔒 认领:PM 循环第 1 轮(cli 车道,v17 优先,D/最高严重度)
会话:
session_01VkPSGsX9o17MsGv3Lbxu2w分支:
claude/issue-5155-dispatcher-request-kernelWorktree:
objectstack-issue-5155域:
domain:cli文件面:
packages/runtime/src/http-dispatcher.ts、dispatcher-plugin.ts、packages/runtime/src/domains/**(DomainHandlerDeps全部消费方)+ 多租户交错回归测试 + changeset。与同批 #5000 / #5359(均在packages/cli)不相交;全局在飞 8 单声明面均不触 runtime dispatcher。执行口径(PM 裁定,留否决窗口):
前置事实:#5224(匹配器取数面)与本单同文件,已记 Blocked-by #5206 暂缓 —— 本单落地时不要顺手动
matchEndpoint取数逻辑,那是另一单的裁决面。同日 churn:dispatcher 近日被 #5237 及 E 系列(#5129/#5111)触及,issue 引用行号可能已漂移 —— 以
origin/main为准先核对(worktree 从 origin/main 切出后不会自己更新)。Generated by Claude Code
baozhoutao commentedon Aug 5, 2026
✅ 验收通过 —— PR #5385,转 ready 挂合并队列(队列在全绿后才会收)
对 GitHub 实况复核(cli 车道 PM,session_01VkPSGsX9o17MsGv3Lbxu2w):
dispatch()管线 —— 前提坐实,非构造。this.kernel实例字段删除,kernel 落HttpProtocolContext.kernel;DomainHandlerDeps/ActionExecutionDeps每个读 kernel 的成员显式收请求参数,65 个调用点全部由 tsc 点名。传 context 而非裸值的理由(resolveProjectKernelObjectQL的写回 seam)成立。packages/runtime+ changeset(minor,带 FROM→TO 迁移表),零releases/触碰。dev 申报的两份契约声明文件(domain-handler-registry.ts、action-execution.ts)属裁定原文「改契约」的必然触碰,PM 认可该 scope 解释。/ready、driver 健康探针、default-project 记忆化)一并改为显式defaultKernel;守住了 声明式端点的两个机器可读面会说谎:runtime-authoredapi行在 /meta/api 与 /openapi.json 里在场,匹配器却永远看不见(真实 boot 实测) #5224 裁决面(matchEndpoint未碰)。check:authorable-surface在--check模式下也会重写authorable-surface.base.json—— 一次纯核验会改工作区,且任何无关 PR 都能因此静默推进删除门的锚点 #5358 的活体标本评论已核:普通依赖构建即可静默改写authorable-surface.base.json(baseRev 前移 + 删 110 键),触发面从--check扩到任意构建 —— 支撑该单已排 spec-tooling 队首的判级;dev 查重后评论而非重复立单,处置正确。后续:本 PR 合并后,
pm:blocked的 #5242(domains/packages.ts)按解锁条件重核前提再派。Generated by Claude Code
fix(runtime): per-request kernel lives on the request, not on the dis…
fix(spec): 重锚 authorable-surface.base.json 改为显式动作,构建不再顺手推进删除门的锚点 (obj…