feat(runtime): 端点链接线 —— 策略 → 执行,兜底器全上下文(#5040 E5b) - #5156
Merged
Conversation
派发步命中分支的 501 换成完整链:匹配 → 策略(E4)→ executeEndpointTarget(E5) → 响应映射。cacheTtl 的 Cache-Control 只并进成功答复,错误答复一律不带。 兜底器补三根线: - 每进程构建一次端点限流注册表(与 server 级限流器同一个 resolveCache); - 每请求喂完整 EndpointPolicyContext(headers / remoteAddress / resolveSessionPrincipalId / limiters / trustProxy / logger),并把 answer.headers 写到线上(429 的 Retry-After 此前被丢弃); - 匹配前用 HttpDispatcher.resolveRequestScope(自 dispatch() 原地抽出)解析本 请求的环境 / 身份 / driver,委派调用带调用方 ExecutionContext 运行。 多租户 host 解析不到环境时该步弃权(不写任何东西),不拿默认 kernel 作答。 现网行为零变更:非空 apis: 在 publish 仍被硬拒(E7 前不撤),整条链结构性不可达。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EYGdmvWP1ieZSLqvAW6uyd
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 1 package(s): 21 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
os-zhuang
marked this pull request as ready for review
August 4, 2026 06:45
os-zhuang
enabled auto-merge
August 4, 2026 06:45
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #5129
Part of #5040(E 系列 R3 收拢单)。前置齐备:E4
a70cd0a、E5277eb36均在 main。做了什么
把 E4(策略键)与 E5(执行模块)接成一条链,并把兜底器一直缺的三根线补齐。现网行为零变更:非空
apis:在 publish / validate 仍被硬拒(E7 前不撤),整条链结构性不可达。1.
api-endpoint-step.ts:策略通过分支 → 真正执行命中分支的 501 换成
buildEndpointExecutionContext+executeEndpointTarget,答复按执行结果映射。落点仍在策略链之后的那个分支里 —— 该分支没有策略上下文时不可达,denial 会短路它,所以「接了执行、忘了策略」不是一次改动能靠遗漏做到的事(E4 交接契约第 4 条,形状保持不变)。verdict.responseHeaders(cacheTtl算出的Cache-Control)只并进成功答复。错误答复一律不带:这个头描述的是「客户端可以重用的响应体」,让客户端把一个 401 / 429 / 400 缓存 30 秒比什么都不说更糟。两个「调用方没喂够东西」的 501 分支(无策略上下文 / 无执行接线)保留,措辞改成如实描述。它们在组合后的运行时里不可达 —— 这一点由集成测试钉住,而不是靠删掉诚实的报告来保证。
2.
dispatcher-plugin.ts:兜底器全上下文start()里建一次,与 server 级限流器共用同一个resolveCache。每请求新建会让降级 warn 每请求刷一次,并且丢掉限流器的桶缓存 —— 一个每次调用都重置的预算不是预算。EndpointPolicyContext:req.headers/req.remoteAddress/resolveSessionPrincipalId(与 server 级限流器同一个会话查询,不另起一个)/ 注册表 /config.rateLimit?.trustProxy(与 server 级读同一份声明)/ctx.logger。answer.headers:此前兜底器只写status+ securityHeaders +json,把 429 的Retry-After丢了。一个丢了Retry-After的 429 等于没说话。callData按EndpointExecutorDeps的文档绑定 ——(action, params, driver, scope, ec) => callData(execDeps, action, params, driver, scope, ec),execDeps是 dispatcher 已经在给每个域模块用的那一个对象(新增只读访问器HttpDispatcher.actionExecutionDeps,窄口暴露ActionExecutionDeps视图)。automation槽每请求解析、不缓存(bug(service-automation): flow 节点类型校验跑在插件贡献的执行器注册之前 —— 每个 ADR-0019 approval flow 都被误报「will fail at execution time」 #4771 类),且用不带 envId 的查法 —— 与POST /automation/:name/trigger读同一个占位者,否则「同操作同答案」(§4)就不成立了。metadata则带 envId 解析,与callData为同一请求做 ADR-0049 暴露闸时的查法一致。3. 身份 / 环境:复用
dispatch()的解析(多租户姿态)dispatch()里那段「环境解析 + kernel 切换 + 身份解析」原地抽出为公开方法HttpDispatcher.resolveRequestScope(context, cleanPath),dispatch()改为调用它,行为逐字节不变;兜底器调用同一个方法。抄一份到消费者侧会是第二份更弱的身份解析,而 #4936 正是「数据调用没有ExecutionContext」的现场记录:它会以 system 身份读、绕过 RLS。顺序是结构性的:解析必须在匹配之前。多租户 host 上
matchEndpoint必须问本请求自己的 kernel,否则一个租户的声明会决定另一个租户的 URL。弃权姿态(本 PR 采用):
isMultiTenantHost()为真且解析不出environmentId时,该步写不出任何东西(保留传输层原本的 404)并 warn,而不是拿默认 kernel 的数据作答。多租户验收随 E8。代价控制:所有解析都在
isAppEndpointPath()之后才发生 —— 碰不到端点保留段的未匹配请求,今天花多少,现在还是花多少(零)。4. 台账
* /apps/**行的「EXECUTION IS NOT WIRED」改为如实描述:已接线(策略链 → 委派),script/proxy与inputMapping/outputMapping不执行且答 501,publish 门仍未翻转所以结构性不可达(#5078 纪律)。验证
真实 boot 集成用例(
dispatcher-plugin.endpoint-fallback.integration.test.ts,LiteKernel + 真实 Hono + 真实兜底器,走真实 socket;stub 只打在 kernel 服务边界上,socket 到 stub 之间全是生产路径):object_operationfind → 200,体是/data的体(success(result),{object, records, total}),经真实action-execution.callData(无 protocol 服务 → ObjectQL 回退路径);flow→ automation 槽被调用,上下文是buildAutomationContext的形状({recordId, objectName, params}翻译 +< objectName >Id驼峰别名 +userId身份);authRequired默认 → 匿名 401 UNAUTHENTICATED,带会话 → 200;rateLimit→ 第二次 429 且Retry-After在线上(这是本 PR 补的丢头回归的钉子:步一级的断言一直是绿的,只有 socket 上的断言能抓住它);cacheTtl→ 成功答复带Cache-Control: private, max-age=30;同样声明cacheTtl但执行失败(get无?id=→ 400VALIDATION_FAILED+fields[])的答复不带;Allow→ E3 的否定断言逐条保持;matchEndpoint抛错 → 5xx 包络;范围外(已如实归档)
inputMapping/outputMapping仍未实现 —— E5 遗漏面:端点 inputMapping / outputMapping 未实现(设计 §3.4 的 api-mapping.ts),E7 翻转前必须补 #5137 已在跟踪,E7 翻转前必须补;HttpDispatcher把「本请求的 kernel」存在实例字段this.kernel上,多租户 host 上并发请求可跨await互相串改。先于本单存在,本单只是多了一个同样写法的写入方(解析块是原地抽出的);修法要穿过domainDeps的每个消费者,不该混进接线单。🤖 Generated with Claude Code
https://claude.ai/code/session_01EYGdmvWP1ieZSLqvAW6uyd