Skip to content

17.x 立项:建设声明式 ApiEndpoint 执行器(挂载 + matchEndpoint + authRequired/cacheTtl/inputMapping/outputMapping 逐键接线) #5040

Description

@os-zhuang

维护者已于 #4936(2026-08-04 00:20Z 裁决)批准立项,本卡为裁决第 2 条的落项位,不在 v17 窗口内执行:

17.x 内:建设端点执行器(挂载 + matchEndpoint + authRequired/cacheTtl/inputMapping/outputMapping 逐键接线),建成后把拒绝换成执行——词表零折腾。执行器立项单独开卡,不挤本单。

Blocked-by: #4936

范围(自 #4936 裁决与实测证据继承)

  1. 路由挂载:dispatcher-plugin 目前逐条显式注册路由,元数据声明的路径无 catch-all(声明式 apis:(ApiEndpoint)入站面全链路零执行:元数据装载成功、路由从未挂载、matchEndpoint 全仓无实现 #4936 实测两处断链之一)。执行器需要给元数据声明的端点一条真实进入 dispatcher.dispatch() 的通路。
  2. matchEndpoint:在元数据服务上真实实现(声明式 apis:(ApiEndpoint)入站面全链路零执行:元数据装载成功、路由从未挂载、matchEndpoint 全仓无实现 #4936 时全仓无实现,handleApiEndpoint 恒 { handled: false },该死分支已随 声明式 apis:(ApiEndpoint)入站面全链路零执行:元数据装载成功、路由从未挂载、matchEndpoint 全仓无实现 #4936 的 PR 摘除,本卡是重建而非复活旧代码)。
  3. 逐键接线:authRequired(⚠️ 安全语义,默认 true 生效即 breaking 面)、cacheTtl、inputMapping / outputMapping;rateLimit 两键随 「v17」入站 rateLimit 接执行:ApiEndpoint / HttpServer 的 RateLimitConfig 推导为 runtime token bucket 配置,dispatcher 生效(#4686 拆向之一) #4910-Q2 裁决在此接线(复用 feat(spec,runtime,hono)!: 入站 rateLimit 从零建 seam —— 授权预算真正产生 429 (#4910, #4937) #5006 建的 server 级 seam 范式:principal 优先 key、kernel-cache 计数)。
  4. 完成判据:v17 落地的「非空 apis: publish/validate 硬拒」(声明式 apis:(ApiEndpoint)入站面全链路零执行:元数据装载成功、路由从未挂载、matchEndpoint 全仓无实现 #4936)换回执行;ApiEndpointSchema 词表零折腾(裁决依据:端点词表是行业极稳定形状)。

分诊注记

落点跨域:挂载在 packages/runtime(按域表归 domain:cli,取主落点标之)、matchEndpoint 在 packages/metadata*(domain:engine)、词表在 packages/spec(domain:spec)。被取用时按 contract-first 拆分(spec 子单先行),不整卡直派。17.x 窗口打开、#4936 的 PR 合入之前,本卡不入 pm:queue。

关联:#4936(裁决全文)、#4939(ApiRegistry 同批退役,词表收敛为一套)、#4910 / PR #5006(server 级 rateLimit seam 范式)、ADR-0049、ADR-0076。

Activity

  1. os-zhuang commented on Aug 4, 2026

    @os-zhuang
    ContributorAuthor

    阶段二设计文档(设计先行,PM 复核通过)

    出品:执行器车道 PM 会话 session_01EYGdmvWP1ieZSLqvAW6uyd 派发的只读设计 agent;设计基线 origin/main @ 70c0769(2026-08-04)。PM 复核结论:裁决继承完整(#4910-Q1=B/Q3=C/Q4=B、#4936 响亮拒绝→翻转)、词表冻结假设贯穿全文、spec 触碰件全部标注 [转回 spec 车道]、未决项列而不猜。

    维护者否决窗口 —— 以下三处在 E 系列子单派发前需明确认可(不阻塞今天的任何事,阶段一在飞):

    1. §1 推荐 C(IHttpServer.setFallbackHandler? 契约扩面)vs 备选 A(通配路由,带顺序病灶);
    2. §3.3 cacheTtl 语义为本设计所定(GET-only、principal 入键为安全红线、kernel-cache 存储);
    3. §7-3 script / proxy 维持 publish 拒绝待单独立卡(proxy 是 SSRF 面,须安全裁决)。

    子单(E1–E8)将在阶段一 PR 落地后按表开立;E1/E7 属 spec 车道,届时以 pm:queue 转回主队列 spec PM。


    17.x 声明式 ApiEndpoint 执行器设计(#5040)

    设计基线:origin/main @ 70c0769(2026-08-04)。本文按 阶段一落地后的世界 设计:v17 已对非空 apis: publish/validate 硬拒(#4936 裁决),handleApiEndpoint 死分支、/__api-endpoint 遗留前缀、ApiRegistry 全家(#4939)均已摘除。ApiEndpointSchema(packages/spec/src/api/endpoint.zod.ts)词表冻结,本设计纯消费;凡本文标记 [转回 spec 车道] 的条目,一律经 pm:queue 转交主队列 spec PM,不在执行器车道直做。

    裁决继承(⛔ 不重议):#4910-Q1=B(server: seam)、Q3=C(principal 优先 key + trustProxy 门控)、Q4=B(kernel-cache 共享计数);#4936 2026-08-04 裁决(响亮拒绝 → 建成后翻转,词表零折腾)。

    全文两条固定评估轴:长远合理性(项目长期健康)与 防 AI 写元数据犯错(声明即执行,写错在最早的关口被打回,不存在「解析通过然后什么也不发生」的中间态)。

    0. 词表与现有管线盘点(设计输入)

    冻结词表(endpoint.zod.ts,defaults 由 Zod 物化):

    键 形状 本设计的消费点
    name ^[a-z_][a-z0-9_]*$ 匹配索引主键、限流桶命名空间、缓存键、OpenAPI operationId
    path / method ^\/ 绝对路径 / HttpMethod matchEndpoint 匹配维度
    type flow | script | object_operation | proxy 17.x 只执行 object_operation + flow;script/proxy 维持 publish 拒绝(见 §3.4、§5)
    target / objectParams 目标 ID / {object, operation} 执行目标委派(§4)
    authRequired default true 匿名拒绝门(§3.1)
    rateLimit RateLimitConfigSchema(enabled/windowMs/maxRequests) 复用 #5006 seam(§3.2)
    cacheTtl 秒 响应缓存(§3.3)
    inputMapping / outputMapping {source, target, transform?}[] 纯映射模块;transform 维持拒绝(§3.4)
    summary / description 文档 /openapi.json(§3.5)

    现有可复用管线(全部实测于 origin/main):


    1. 挂载:声明路径如何进入 dispatch()

    #4936 断链之一:dispatcher-plugin 逐条显式注册路由,声明路径落进 Hono 裸 notFound({"error":"Not found"}),连 dispatcher 的语义 404 都到不了。

    选项

    选项 机制 长远合理性 防 AI 犯错
    A. 通配 catch-all 路由 dispatcher-plugin start() 末尾注册 ${prefix}/* 五个动词的通配路由 → dispatch() ⚠️ Hono 同路径多 route handler 是先注册者赢,通配是否遮蔽 REST 的 /data 静态路由取决于插件 start 顺序——这正是 ADR-0076 D11/OQ#9 点名过的 first-registration-wins 病灶,只能靠顺序约定+一致性测试压住 ✅ 无中间态
    B. publish/装载时逐条注册真实路由 元数据发布时向 IHttpServer 注册具体路由 ❌ 三重结构性错位:① 挂载是 host 全局的而元数据是 per-environment 的(dispatcher-plugin 自己的注释就写明「mounts are host-global while the … service lives in the per-project kernel, so presence is a per-request question」);② 多节点部署要跨实例同步路由表,等于用 HTTP 路由表复制一份元数据状态,两处真相;③ 运行中向 Hono 增删路由的行为(matcher 冻结)未经本仓验证 ⚠️ 发布成功≠每个节点都挂上了,失败静默
    C. 传输层 notFound 兜底 seam(推荐) IHttpServer 增可选 setFallbackHandler(handler)(Hono 侧落在 app.notFound);dispatcher-plugin 注册兜底器:对 ${prefix}/ 下未匹配请求走 dispatch()(其中含端点匹配步),其余维持原 notFound ✅ 结构上不可能遮蔽任何显式路由——兜底器只在全部路由未命中后运行,零顺序依赖,ADR-0076「一条路由一个属主」以构造保证而非约定保证;顺带把 /api/v1/* 的裸 404 升级为 dispatcher 语义 ROUTE_NOT_FOUND 包络(#4936 记录的裸/语义 404 分裂就此收口) ✅ 未声明路径得到带 hint 的语义 404,AI 调试时能分清「路由不存在」与「端点没执行」

    推荐 C,A 为备选(若维护者不愿为此扩 IHttpServer 契约——该契约在 packages/spec/src/contracts/http-server.ts,故 C 的契约面是 [转回 spec 车道] 条目;适配器实现在 plugin-hono-server 不受限)。中间件 seam(use())不可用:其契约明确 req.body 不填充(adapter.ts:362),而 flow/create 端点必须读 body。

    冲突与优先级规则(对 ADR-0076 的回答)

    1. 内建永远赢,且在 publish 期就赢:声明路径的首段(${prefix} 后第一段)落入保留族即 publish 硬拒。保留族 = domain registry 全部前缀(/health /ready /analytics /i18n /notifications /security /keys /ui /share-links /packages /automation /auth /ai /actions /mcp /meta /data,见 http-dispatcher.ts:296 起)∪ LEGACY_CHAIN_PREFIXES(route-ledger.ts:92)∪ REST 专属族(/batch /search 等)∪ /environments /projects(scoping 前缀)。拒绝信息带处方(「该前缀由内建 <domain> 拥有,换一个业务前缀,如 /api/v1/<app>/…」)。防 AI 轴上这是最重要的一条:AI 最常犯的错就是把端点声明在 /api/v1/data/... 下然后困惑为什么行为不对——现在它在 defineStack 就被打回。
    2. 运行期兜底器永不竞争:方案 C 下它只见没人要的请求,冲突在构造上不存在;即便走备选 A,匹配器里也只有过了 publish 门的路径。
    3. 声明路径互撞(同 stack 内同 method+path 两条):publish 期硬拒(§5 的 build 门);绕过 publish 直接 metadata.register() 写入的互撞:匹配器装载期按 name 字典序保留第一条、error 级日志点名被弃者(确定性 + 响亮,不掷硬币)。
    4. 保留族列表的双端一致性:spec 侧 build 门无法 import runtime,故列表在 spec 侧 pin 一份常量,runtime 侧新增一致性测试断言「pin 列表 ⊇ 实际 registry 前缀 ∪ ledger」,漂移即红。该测试落 runtime,pin 常量落 spec [转回 spec 车道]。
    5. route-ledger.ts 增一行兜底器条目(disposition: 'dynamic'),route-ledger.conformance.test.ts 同步——阶段一删掉的 /__api-endpoint 行不复活,新行以真实机制命名。

    边界约束(publish 门的一部分):声明路径必须以运行前缀开头(默认 /api/v1)。兜底器只接管前缀之下;前缀之外的声明路径 publish 拒绝——否则又造一批「声明了但永远不匹配」的死路径。前缀非默认值的部署见 §7 未决项。


    2. matchEndpoint 契约

    归属

    选项 长远合理性 防 AI 犯错
    A. MetadataManager 实现,匹配器为包内纯模块(推荐) ✅ 与 #5040 卡面一致(matchEndpoint 落 packages/metadata*);per-environment 作用域免费获得——dispatcher 本来就 resolveService('metadata', environmentId) 拿到的是 per-kernel 实例;失效直接挂在 manager 自己的 notifyWatchers/集群通道上,不新增跨服务事件 ✅ 元数据的解析、默认值物化、失效、匹配同源,不存在「注册表看见了、匹配器还没看见」的窗口语义
    B. 独立 api-endpoint-router 服务 ⚠️ 组合面+1(#4939 刚退役过一个「从未被装配」的独立注册表服务,教训在案);需要自订失效订阅与 env 作用域协议 ⚠️ 服务缺席时端点静默 404,多一个可缺席件

    推荐 A。 契约声明在 IMetadataService 上(可选方法,与 watch?/generateOpenApi? 同风格)——packages/spec/src/contracts/metadata-service.ts 属 spec,[转回 spec 车道];runtime 消费端沿用 typeof === 'function' 探测,契约未合入前不阻塞 runtime 开发。

    签名与语义

    /** IMetadataService(可选成员) */
    matchEndpoint?(query: { path: string; method: string }): Promise<ApiEndpointMatch | undefined>;
    
    interface ApiEndpointMatch {
      /** ApiEndpointSchema.parse 后的形状 —— 默认值已物化(authRequired 缺省即 true) */
      endpoint: ApiEndpoint;
      /** 路径参数。17.x 恒为 {}(v1 不支持模板段,见 §7);预留槽位使未来加模板不破契约 */
      params: Record<string, string>;
    }
    • 匹配维度:method 大写规整后精确比较;path 去尾斜杠后整串精确匹配(不做百分号解码、不做大小写折叠——见 §7 未决)。17.x 不发明模板语法:冻结词表对 :param/{param} 均无定义,发明即造「跑在实现里的隐藏方言」。get/update/delete 的记录 id 一律取 query.id(阶段一前死代码即此语义,showcase 两端点均为静态路径,零损失)。
    • 索引与缓存:首次调用惰性构建 Map<'METHOD path', entry>,数据源 list('api');每条经 ApiEndpointSchema.safeParse,解析失败的存量条目跳过并 error 日志点名(防 AI 轴:坏条目响亮缺席,绝不半执行)。返回的是 parse 后对象,执行器永远不读原始存储 JSON。
    • 失效:manager 内部挂自身 notifyWatchers('api', …);集群写入本就经 metadata.changed 通道汇入同一函数(metadata-manager.ts:1748),多节点失效免费。失效动作 = 置脏整索引(端点量级为个位数到百位,重建成本可忽略,不做逐条增量)。
    • 多租户:无 env 参数——实例即作用域,与仓内其余 metadata 消费完全同构。

    3. 逐键接线与激活顺序

    总原则(declared = enforced 的落法):v17 硬拒把整面清了场——没有任何存量栈能带着非空 apis: 活到 17.x,所以不存在「逐键渐进激活」的兼容问题;所有键在翻转前整体不可达(publish 仍拒),翻转后整体生效。绝不出现「某键已宣传支持、runtime 未交付」的中间态:凡 17.x 不执行的子集(script、proxy、mapping transform),publish 继续拒绝该子集而不是解析后忽略(§5)。

    请求期管线顺序(在兜底器→dispatch()→端点步内,executionContext 此时已由 dispatch 解析完毕):

    matchEndpoint 命中
      → ① rateLimit(429 + Retry-After)
      → ② authRequired(401)
      → ③ cacheTtl 命中查(仅 GET)
      → ④ inputMapping
      → ⑤ 目标执行(object_operation / flow)
      → ⑥ outputMapping
      → ⑦ cacheTtl 回填
      → 统一包络出口
    

    ①先于②:限流不区分 401 与 200 消耗,凭据爆破恰是匿名流量,先②会让扫号者绕开计量。③在②后:缓存永远不对未过认证门的请求应答(且键内含 principal,双保险)。

    3.1 authRequired(⚠️ 安全语义,默认 true)

    复用 dispatch 已解析的 executionContext + shouldDenyAnonymous({userId, isSystem})(@objectstack/core,/meta 域同款,#3963 起无条件)。authRequired !== false 且匿名 → 401 ANONYMOUS_DENY 包络。默认值由 schema parse 在匹配器内物化,执行器读不到「缺省」这个状态——AI 忘写 authRequired 得到的是安全默认,写 authRequired: false 是唯一的开门方式,且 diff 里可 review(与 mountRouteOnServer 的 route.auth !== false 语义对齐)。不新建任何 auth seam。

    3.2 rateLimit(裁决继承:Q3=C、Q4=B)

    复用 #5006 原语,不新写限流器:deriveBucketConfig(endpoint.rateLimit)(含 0/负值 boot 期拒绝)→ 每端点一个 SharedTokenBucketLimiter(按 endpoint.name 缓存,匹配器失效时同步失效)→ key = apiep:{endpoint.name}: + resolveRateLimitKey(...)(principal 优先、匿名回落 IP、转发头仅在 server.trustProxy 下取信——trustProxy 从 dispatcher-plugin 既有 config.rateLimit.trustProxy 透传,端点级与 server 级读同一份声明,不造第二个信任开关)。计数走 createLazyCounterStore(kernel-cache 共享,降级一次性 warn 带后果)。命名空间前缀保证端点桶与 server 桶互不侵蚀;server 级中间件在前、端点级在后,两层预算按声明各自独立生效(server 级是全局底线,端点级是业务配额)。429 经 buildApiError + Retry-After,与 #5006 完全同构。

    3.3 cacheTtl

    词表只说「Response cache TTL in seconds」,执行语义欠定,本设计定为(标注给维护者 review):

    • 位置:kernel cache 服务(ICacheService.set(key, value, ttlSeconds))——与 Q4=B 同一存储哲学,多节点命中一致;无 cache 服务时不缓存并一次性 warn(降级响亮)。
    • 范围:仅 method: GET 端点缓存;其余方法上写了 cacheTtl → publish 拒绝(处方:「cacheTtl 仅对 GET 端点有效」)而非静默忽略 [转回 spec 车道,随 §5 翻转 PR]。
    • 键:apiepc:{env}:{endpoint.name}:{principalId|anon}:{规范化 query}。principal 必在键内——authRequired 端点的响应是 RLS 裁剪过的,跨主体共享缓存即数据泄漏;这条是安全规则不是调优项。
    • 客户端侧:附 Cache-Control: private, max-age=<ttl>;命中回 X-Cache: hit(验收可观测,§5)。
    • 失效:端点元数据重发布 → 匹配器失效钩子顺带删该端点键空间(TTL 本身兜底数据变更——cacheTtl 语义即「容忍最多 ttl 秒陈旧」,不做按对象写失效,那是它没承诺的)。

    3.4 inputMapping / outputMapping

    • 纯模块 api-mapping.ts(runtime):source/target 按点路径 get/set;inputMapping 作用于 {...query, ...body} → 目标入参;outputMapping 作用于目标结果 → 响应体;无 mapping 则原样直通(与今日 /data、/automation 行为一致)。
    • transform 维持 publish 拒绝:全仓不存在「transformation function name」注册表,而 stack.zod.ts:751 已有同名先例——mapping transform 因无服务端沙箱必须 build 期失败(framework#2611)。解析后忽略 = 教科书式 declared≠enforced。拒绝文案给处方(「用 inputMapping 的字段改名/搬运能力;函数式变换待 transform 注册表立项」)[转回 spec 车道,随翻转 PR]。

    3.5 summary / description → /openapi.json

    dispatch() 今天已探测 metaSvc.generateOpenApi(http-dispatcher.ts:1612)——seam 存在,实现缺席(全仓无实现,该路由恒落 404)。推荐:在 MetadataManager 上实现 generateOpenApi(与 matchEndpoint 同源同失效),仅从已装载 api 条目产出 paths(method/path/summary/description/authRequired→securityRequirement、operationId=name)。契约面(IMetadataService.generateOpenApi?)[转回 spec 车道]。不触碰 documentation.zod.ts 的 ApiDocumentationConfig——那是另一套(疑似死的)词表,本设计不给它续命。


    4. 执行目标与错误包络

    统一原则:声明式端点 = 既有执行管线的「稳定 URL 别名 + 策略层」,不是第二套执行方言。 同一操作经声明端点与经内建路由的答案必须一致——长远轴上避免双管线漂移,防 AI 轴上意味着端点语义可以从既有文档直接推知。

    type: object_operation

    委派 action-execution.callData(deps, <op>, {object, ...}, undefined, environmentId, executionContext):

    • 操作映射:find→'query'(响应 success(result.records, {total}),与 spec FindDataResponse 对齐)、get/create/update/delete 同名直达;五个枚举值全部接通(阶段一前死代码只写了三个,那不是契约)。入参:find ← query(经 inputMapping);get/delete ← query.id;create/update ← body(经 inputMapping),update 另取 query.id。
    • 必须传 executionContext——被摘除的死代码调 callData('query', {object, query}) 时丢弃了身份,真跑起来就是以 system 身份绕过 RLS 读数。本设计把这条写成红线并配测试:声明端点的读写与 /data 同请求同身份逐字节同结果(RLS/FLS/apiEnabled 门三者经 callData 免费继承)。
    • objectParams 缺失或 object/operation 缺失:publish 拒绝(type: object_operation 必须带全)[转回 spec 车道,随翻转 PR]。

    type: flow

    委派 automationService.execute(endpoint.target, buildAutomationContext(mappedBody, context)):

    错误包络

    所有出口经 deps.success/error/errorFromThrown,兜底器侧经 errorResponseBase——buildApiError 是唯一造错体的地方,error-envelope.conformance.test.ts 的源码扫描会强制新 domain 文件合规。thrown 的 status/statusCode/ValidationError.fields[] 语义(#3867/#3918)自动继承;5xx 消息过 looksLikeInternalErrorLeak 消毒。


    5. 拒绝 → 执行的翻转

    选项 评估
    环境变量 feature flag ❌ 制造第三态(声明合法+执行与否看部署),flag 会赖着不走;ADR-0049 的敌人
    版本门(17.x 起自动) ❌ 拒绝逻辑在 spec 的 publish/validate 里,本无版本开关基建
    单一翻转 PR(推荐) ✅ 拒绝在哪里立的就在哪里撤;runtime 各件先行合入但结构性不可达(publish 仍拒,真实组合里零暴露;测试经 metadata.register() 直注条目驱动,不走 publish)

    翻转 PR 的内容(spec 车道,[转回 spec 车道]):把「非空 apis: 整体硬拒」收窄为「不支持子集精确硬拒」——type: 'script'、type: 'proxy'、mapping transform、保留前缀路径、前缀外路径、非 GET 的 cacheTtl、缺 objectParams 的 object_operation、同 stack 内 method+path 重复;每条拒绝带处方。外加 spec 生成物七路径 regen + changeset。无 D3 semantic migration:v17 已清场,16→17.x 直跳者的 apis: 从「静默无效」变「真执行(含 authRequired 默认 401)」,这是能力新增方向的行为变化,upgrade note 一段说明即可,与 #4910 附带确认同理。

    就绪判据(翻转 PR 的合入门,#4936 实测法,RED-first):

    1. e2e 第一套件在翻转前的形状上断言拒绝仍在(RED 基线,feat(spec,runtime,hono)!: 入站 rateLimit 从零建 seam —— 授权预算真正产生 429 (#4910, #4937) #5006 范式);
    2. 真实 boot(pnpm dev -- --fresh,showcase,同一会话 cookie):
      • GET /api/v1/showcase/tasks → 200 任务列表,与 GET /api/v1/data/showcase_task 同身份同结果(阳性对照);
      • 匿名同路径 → 401 ANONYMOUS_DENY 包络(authRequired 默认生效的直接证据);
      • POST /api/v1/showcase/inquiries/purge → 200 且 janitor flow 真实删除了记录(不是 200 空壳);
      • cacheTtl: 30 命中:第二次 GET 带 X-Cache: hit;
      • 临时加 rateLimit 的端点:超预算 → 429 + Retry-After,双 principal 各自计桶;
      • 未声明路径 GET /api/v1/showcase/nope → dispatcher 语义 ROUTE_NOT_FOUND 包络(不是 Hono 裸 {"error":"Not found"});
    3. showcase 两端点声明恢复(阶段一注释/移除的回迁)+ coverage.ts 的 apis 条目改回真话(引用新执行器机制,不再提 handleApiEndpoint);
    4. /api/v1/meta/api 与 /openapi.json 均如实反映两端点。

    6. 子单拆分(contract-first,一单一落面)

    # 内容 车道 主要文件面 Blocked-by
    E1 契约:IMetadataService.matchEndpoint? + generateOpenApi? + 契约测试;(若采 §1 方案 C)IHttpServer.setFallbackHandler? [转回 spec 车道] packages/spec/src/contracts/metadata-service.ts(+.test.ts)、packages/spec/src/contracts/http-server.ts(+.test.ts) #4936 阶段一 PR
    E2 匹配器:纯匹配模块 + 惰性索引 + safeParse 装载门 + watcher/集群失效 metadata(engine) packages/metadata/src/endpoint-matcher.ts(新)、metadata-manager.ts、测试 E1
    E3 挂载 + 执行步骨架:兜底 seam(hono adapter + dispatcher-plugin)、dispatch() 端点步(匹配→语义 404)、route-ledger 行 + conformance runtime(cli) packages/plugins/plugin-hono-server/src/adapter.ts、packages/runtime/src/dispatcher-plugin.ts、http-dispatcher.ts、domains/api-endpoint.ts(新)、route-ledger.ts E1、#4936 阶段一 PR(http-dispatcher.ts 文件面重叠)
    E4 策略键:authRequired / 端点级 rateLimit(复用 #5006 原语)/ cacheTtl runtime domains/api-endpoint.ts、security/inbound-rate-limit.ts(导出复用)、测试 E2、E3
    E5 执行目标 + 映射:object_operation 五操作经 callData(带 executionContext 红线测试)、flow 经 execute + 导出 buildAutomationContext、api-mapping.ts runtime domains/api-endpoint.ts、domains/automation.ts(导出)、api-mapping.ts(新)、测试 E2、E3
    E6 generateOpenApi 实现(summary/description → /openapi.json) metadata packages/metadata/src/metadata-manager.ts(或伴生模块)、测试 E2
    E7 翻转 PR:硬拒收窄为不支持子集 + 全部 publish 门(§5 清单)+ 保留前缀 pin 常量 + spec regen + changeset/upgrade note [转回 spec 车道] packages/spec/src/stack.zod.ts、生成物七路径、.changeset/ E2–E6
    E8 验收:showcase 端点恢复 + coverage.ts 修真 + RED-first e2e + 真实 boot 探针(§5 清单)+ 保留前缀双端一致性测试 runtime/examples examples/app-showcase/src/system/apis/、coverage.ts、packages/runtime e2e E7

    每单量级均在单次 os-dev run 内;E2/E3 可并行(文件面不相交);E4/E5 可并行(同文件不同函数面,若担心冲突则串行 E4→E5)。spec 触碰(E1、E7)一律 pm:queue 转回主队列 spec PM,认领时按 #4936 惯例声明完整文件面对账。


    7. 风险与未决(不猜,列为 open questions)

    代码无法回答、需实现期验证或维护者裁决:

    1. Hono 路由器的通配-vs-静态优先级与运行中加路由行为未经本仓验证——这是推荐 §1 方案 C(结构性免疫)而非 A 的核心原因;若 C 因契约面被否退回 A,必须先在 @objectstack/http-conformance(ADR-0076 OQ#10 的跨适配器套件)补优先级断言。
    2. 路径模板:冻结词表未定义 :param/{param} 语法。v1 精确匹配 + query.id 是不发明语义的最小面;是否补模板语法属词表动作 [转回 spec 车道候选,不假设]。ApiEndpointMatch.params 槽位已为此预留。
    3. script / proxy 执行语义:IAutomationService.execute 注释称「flow or script」,但 script 目标是否真的经 execute 可达未验证;proxy 则是全新出网面(SSRF/出口策略),安全语义必须单独立卡裁决。17.x 两者维持 publish 拒绝(带处方)——这是 declared=enforced 的正确姿势,不是欠账。
    4. cacheTtl 的精确语义(服务端缓存 vs 纯 header、principal 键、GET-only)为本设计所定,词表未定;§3.3 的安全规则(principal 入键)请维护者确认为红线。
    5. 匹配的字符层语义:百分号编码、Unicode 规整、大小写——v1 按原串精确比较;是否需要 RFC 3986 规整化留观察(写进 matchEndpoint 的 describe/文档)。
    6. draft vs published:MetadataManager 有 publishedDefinition 快照(ADR-0033),但 list('api') 返回的是工作定义还是发布定义、其他执行面(flow)如何取舍,未从代码验证;匹配器应与 flow 装载采同一策略,实现期对齐并写测试钉住。
    7. composeStacks 静默丢弃非数组顶层键(composeStacks silently drops every non-array top-level key — api: today, server: as of #4910 #5005):apis: 是数组键应不受累,但该单的合并语义裁决未落,组合栈路径的端点装载在 E8 验收中必须显式覆盖。
    8. 前缀非 /api/v1 的部署:声明路径含前缀字面量(showcase 即 /api/v1/...),部署改 config.prefix 后声明与运行前缀脱钩。v1 以 publish 门(「路径必须落在运行前缀下」)暴露问题;是否引入前缀相对声明属词表动作 [转回 spec 车道候选]。
    9. scoped URL 变体(/environments/:environmentId/...):声明端点是否需要 scoped 别名(如 automation/AI 路由那样双挂)未定;v1 不提供,多租户经 host 头/kernel 解析已可达,显式 scoped 别名留观察。
    10. 词表变更需求:目前为零 ——本设计全程未发现必须改 ApiEndpointSchema 的点;上列 2/8 两条为「若产品要」的候选,均不预支。

    Generated by Claude Code

  2. os-zhuang commented on Aug 4, 2026

    @os-zhuang
    ContributorAuthor

    维护者三项裁决(2026-08-04)—— 设计文档否决窗口关闭,方向落定

    1. 命名空间制:声明式端点路径收紧为 /api/v1/apps/<应用名>/<子路径>,apps 为唯一平台保留切出段,应用段派生自 stack 身份。设计 §1 的保留前缀 pin 清单 + 双端一致性测试整体作废(由命名空间规则替代),§7-8(前缀脱钩)大幅收窄;
    2. 通道分工:actions = 平台内命令(会话调用方),apis = 平台外集成面(资源 + webhook)。判据写进两侧 schema describe(spec 车道执行项);
    3. type: flow 保留,三条纪律:分工判据入 describe、同管线红线(§4 已立)、E7 新增 publish 门:authRequired: false 必须伴随 rateLimit,否则拒绝附处方;签名验证列为将来词表候选,不预支。

    裁决全文与依据(含主流平台调研)见 ADR 起草单 #5060(已派发,docs-only PR,预计 ADR-0120);ADR 落地不晚于 E 系列开派。对子单表的影响:E7 的 publish 门清单更新为 —— 命名空间规则(替代保留前缀族)、匿名须限流、不支持子集拒绝(script/proxy/transform/非 GET cacheTtl/缺 objectParams/同栈 method+path 重复);E1/E3 因保留族一致性测试作废而略减面。§1 挂载机制(方案 C vs A)与 §3.3 cacheTtl 语义两处否决窗口维护者未提出异议,按设计推荐执行。


    Generated by Claude Code

  3. os-zhuang commented on Aug 4, 2026

    @os-zhuang
    ContributorAuthor

    ADR-0121 已合入 main(PR #5064,经合并队列落地;#5060 闭环)。E 系列开派的 ADR 前置就此满足 —— 剩余前置仅 #4936 阶段一 PR(在飞)。执行口径固化为:E7 落 D1/D2 命名空间 publish 门(Q1 采 A:声明 apis: 的 stack 必须显式 manifest.namespace,无派生回落)+ D6 匿名须限流 + 不支持子集精确拒绝;D3 判据入两侧 describe(spec 车道执行项);showcase 端点按 /api/v1/apps/showcase/… 形状随 E8 回迁。


    Generated by Claude Code

  4. os-zhuang commented on Aug 4, 2026

    @os-zhuang
    ContributorAuthor

    阶段一 PR #5065 已验收(全绿),入队中。两条 E 系列执行注记:

    1. E7:v17 拒绝文案里「your endpoint definitions stay valid」在 ADR-0121 命名空间制下需修正 —— 翻转 PR 重写该文案时,把「path 须迁移为 /api/v1/apps/<namespace>/<子路径> 形状」写进处方(showcase 回迁同理,E8);
    2. E6:GET /openapi.json 有两个属主:rest-server 真serve,http-dispatcher 的 generateOpenApi 分支全仓无实现(ADR-0076 D1 影子重复) #5078 实测发现 GET /openapi.json 已由 packages/rest/rest-server.ts:2523 真实拥有(dispatcher 侧 generateOpenApi 探测分支全仓零实现,恒 fall-through)。设计 §3.5 在 MetadataManager 上实现 generateOpenApi 之前,必须先裁「一路一主」:要么 openapi 面走 rest 侧现有属主(dispatcher 死分支删除),要么属主迁移 —— 不得造成双属主并存。GET /openapi.json 有两个属主:rest-server 真serve,http-dispatcher 的 generateOpenApi 分支全仓无实现(ADR-0076 D1 影子重复) #5078 已标 domain:cli,建议与 E6 合并处置。

    Generated by Claude Code

  5. os-zhuang commented on Aug 4, 2026

    @os-zhuang
    ContributorAuthor

    里程碑:阶段一已落地 main(29c6c9d,PR #5065),落地后复核通过 —— 退役条目零残留(ApiEndpointRegistrationSchema 家族在 api-surface / authorable-surface / manifest 全部消失)、幸存者完好(ApiEndpointSchema、ConflictResolutionStrategy)、硬拒带 #5040 指针在 main、兄弟单 #5006 实现体无损。#4936 / #4939 已关闭并完成标签卫生。

    E 系列状态:维护者裁定 v17 切版后开工(E7/E8 结构上必须在拒绝随 v17 发出之后;E1–E6 不挤 rc 窗口)。经批准,E1 已提前立单占位:#5080(spec 车道队列,Blocked-by: v17 切版,认领前核 .changeset/pre.json)。E2/E3 Blocked-by: #5080,切版信号到达后由本车道按表开派。


    Generated by Claude Code

  6. os-zhuang commented on Aug 4, 2026

    @os-zhuang
    ContributorAuthor

    E6 设计修正(基于 #5078 实测证据,替代设计 §3.5 的原推荐)

    实测已定(全文见 #5078 分诊评论):GET /api/v1/openapi.json 由 rest-server 独占应答(355KB 真文档,Host 注入 / 199 paths 展开 / x-template 三指纹),dispatcher 的 generateOpenApi 探测分支「双重死」(零实现 + 零到达路由)。据此按 ADR-0076 一路一主修正:

    维护者否决窗口照例(不阻塞,E 系列仍等 v17 切版)。另:boot 顺带发现的 /auth/* 500 泄漏已立 #5085(cli 车道,与本程序无关)。


    Generated by Claude Code

  7. os-zhuang commented on Aug 4, 2026

    @os-zhuang
    ContributorAuthor

    🟢 E 系列开工(维护者放行,2026-08-04:「这个需求挺重要的,可以先开发」)

    放行范围:E1–E6 的开发与落地(均结构性不可达,publish 仍拒,零现网行为变更)。E7(翻转)/E8(验收)仍守裁决分期 —— 拒绝必须先随 v17 发出,切版后才派。总表落号:

    单 Issue 状态 Blocked-by
    E1 契约面 #5080 🔵 已派发(branch claude/issue-5080-executor-contracts) —(v17 门已撤,见认领评论)
    E2 匹配器 #5089 待派 #5080
    E3 挂载 seam #5090 待派(与 E2 并行) #5080
    E4 策略键 #5091 待派 #5089 + #5090
    E5 执行目标 #5092 待派(与 E4 并行) #5089 + #5090
    E6 文档面(并入 #5078) #5093 待派 #5092
    E7 翻转(spec 车道) 未立 ⛔ 等 v17 切版 v17 发版 + E1–E6
    E8 验收(showcase 回迁 + e2e) 未立 ⛔ 等 v17 切版 #5093 + E7

    派发节奏:R1 = E1;R2 = E2+E3 并行;R3 = E4+E5 并行;R4 = E6。各单卡上均注明「执行器车道程序卡,其它车道请勿认领」。本车道 PM:session_01EYGdmvWP1ieZSLqvAW6uyd。


    Generated by Claude Code

  8. os-zhuang commented on Aug 4, 2026

    @os-zhuang
    ContributorAuthor

    维护者裁决(2026-08-04,二项)

    1. 不加显式激活开关 —— 依赖两层既有机制:词表安全默认(authRequired 缺省即 true,E1 已物化进契约)+ E7 的 publish 校验门。维护者理由:ObjectStack 应用由 AI 维护,升级文档写明变更,AI 维护者即知 —— 强制审视由升级文档承担,不再由版本通道或开关承担。推论:升级文档从「附带产物」升为「安全承载件」:E7 的 regen upgrade-guide 条目 + E8 的手写迁移指引必须显式包含「审视历史 apis: 配置、尤其显式 authRequired: false 条目」的指令,验收时按安全件复核。
    2. 执行器直接进 v17 —— E7(翻转)/E8(验收)解除切版门,立即立卡入链,v17 切版等 E 系列全绿。原「v17 = 仅拒绝」分期废止;拒绝态成为 main 上的中间历史而非发布过的状态。

    E7/E8 随后立卡补入总表。E2(#5089)已交付 PR #5110,复核中;E3(#5090)开发中。


    Generated by Claude Code

  9. os-zhuang commented on Aug 4, 2026

    @os-zhuang
    ContributorAuthor

    总表更新(E7/E8 立卡,v17 目标生效;E8 的 Blocked-by 即 #5111)

    单 Issue 状态
    E1 契约面 #5080 ✅ MERGED(PR #5097,77be690,落地复核毕)
    E2 匹配器 #5089 🔵 PR #5110 已验收、auto-merge 入队(实现落 packages/metadata,按设计表修正卡面的 runtime 笔误;越范围发现 #5108/#5109 已分诊入队)
    E3 挂载 seam #5090 🔵 开发中(与 E2 并行)
    E4 策略键 #5091 待派(E2+E3 落地后,R3 与 E5 并行)
    E5 执行目标 #5092 待派(R3)
    E6 文档面(并入 #5078) #5093 待派(R4)
    E7 翻转 #5111 已立卡,Blocked-by E4+E5+E6;发布目标 v17;升级文档按安全承载件交付
    E8 验收 #5112 已立卡,Blocked-by #5111;v17 切版等本单绿

    Generated by Claude Code

  10. os-zhuang commented on Aug 4, 2026

    @os-zhuang
    ContributorAuthor

    R3 闭环:E4 #5091(PR #5135,a70cd0a)与 E5 #5092(PR #5136,277eb36)均 MERGED,落地复核零吞并。过程记录:E4 出现一处 NUL 字节缺陷(PM 复核发现并修复,dev 独立收敛,零丢失);两单在 conformance 清单同锚点的预判冲突如期发生,PM 改基保留双条目解决。E5 的映射缺口归并至 #5137(E5c,Blocked-by E5b);#5138(callData 记录不存在三态)观察类持有。E5b(#5129)已派发(branch claude/issue-5129-wire-endpoint-chain)—— 接线后端点链在真实 boot 上打通(仍受 publish 硬拒保护,结构性不可达)。后续:E5c/#5137 → E6 #5093 → E7 #5111(门校验四要点已挂该单)→ E8 #5112 → v17 切版。


    Generated by Claude Code

  11. os-zhuang commented on Aug 4, 2026

    @os-zhuang
    ContributorAuthor

    🏁 E7 翻转落 main(d21c001),收官双单并行开发中

    单 Issue 状态
    E1–E6 + E5b/E5c — ✅ 全部 MERGED,逐单落地复核零吞并
    E7 翻转 #5111 ✅ MERGED(PR #5188):五道门挂 schema superRefine,五条 publish/validate 入径全覆盖;升级文档安全承载件(declarative-apis-endpoints-live)落 registry 经 regen 发布;canonical 路径上移词表单点拥有
    E7b 门兜底 #5189 🔵 开发中(E7 dev 发现的绕行:publishPackage/直写不过门,D6 无运行时对偶 —— publish 层接门 + 匹配器装载期拒绝,双层)
    E8 收官验收 #5112 🔵 开发中(showcase 回迁 + RED-first e2e + 真实 boot 探针 + 升级文档安全复核 + 多租户交错;#5168 悬空 $ref 不计失败)

    E7b + E8 双绿 = E 系列完结 = v17 可切(维护者裁决:执行器直接进 v17,切版等全绿)。


    Generated by Claude Code

  12. 9 remaining items

  13. os-zhuang commented on Aug 4, 2026

    @os-zhuang
    ContributorAuthor

    E9 补记(#5238,PR #5246,c142ced):文档面追平完成 —— App.apis 墓碑与 server 模块头的失真半句摘除(历史保留,双侧钉法)、http-protocol.mdx 新增 Declarative Endpoints 节、quick-reference.mdx 速查条目、objectstack-api skill 按现状教学(以 objectstack validate 处方为真源)。文档断言全部实测(defineStack 三探针)。衍生:#5244(→#5231 子单)、#5245(skill compatibility 行,finding 留维护者裁写法)。

    至此 #5040 程序全部十二单(E1–E9 + E5b/E5c/E7b)完结,文档/skill/例子三层齐平,v17 可切。执行器车道按维护者指令暂停。


    Generated by Claude Code

  14. os-zhuang commented on Aug 4, 2026

    @os-zhuang
    ContributorAuthor

    关闭(维护者指示,2026-08-04)

    spec 车道 PM(session_01ErbEDVAg1No9gdg1pgDAGB)受维护者之问复核本卡,代已暂停的执行器车道收口。核验(不取评论自证,取 GitHub/git 实据):

    v17 切版就绪状态不变,由切版清单(终局重测 + changeset pre exit)跟进,不再以本卡承载。


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions