越范围发现,记录于 #5271 实现期,不在该 PR 内修(#5271 的范围是注册表与 schema 绑定)。与 #5206 的两步都不重合 —— 它是两步都落地之后剩下的那条路。
事实
packages/metadata-protocol/src/protocol.ts 里没有任何 endpoint-publish-gate 的导入(grep validateApiEndpointDeclarations / identityFreeEndpointGateFailure 在该包内零命中)。而 saveMetaItem 明确支持 direct-active 写入(该文件自己的注释:「saveMetaItem(draft AND direct-active saves)」)。
于是三条通路的门覆盖是这样的:
| 通路 |
跑哪道门 |
命名空间门(D1/D2) |
去重门 |
stack 工件(defineStack({ apis })) |
全量门(ObjectStackDefinitionSchema) |
✅ |
✅ |
publishPackage / publishPackageDrafts(#5189 / PR #5279) |
全量门(有 manifest.namespace) |
✅ |
✅ |
PUT /meta/api/:name,state: active |
无 |
❌ |
❌ |
第三条路唯一的守卫是装载期兜底(#5189 / PR #5203),而它按设计跑的是免身份门 —— 命名空间门与去重门恰恰是它跳过的那两道(门函数自己的 TSDoc 写明了这一点)。
后果
一条运行时直写的 active api 行,可以声明落在别的应用的 carve-out 里的路径(/api/v1/apps/OTHERAPP/...)。免身份兜底不看命名空间,所以它会被正常索引;端点步骤只要求路径在 /apps/ 之下。ADR-0121 D1 的原话是「路由归属靠构造而非靠约定加检查」成立 —— 但这条路上没有任何一处在构造时检查归属。
去重同理:同一 METHOD + 归一化 path 的第二条声明,在装载期由 buildEndpointIndex 按字典序裁决并 error 点名,但没有人在写入当场拒绝。
#5271 不改变这条路的现状,它只是给这条路加了一道形状门(422);servability 的判据本来就不在注册表这一层。
建议(不预判)
两个方向,取舍留给裁决:
判据不应产生第二份 —— 无论走哪条,跑的都得是 endpoint-publish-gate.ts 里那个 firstFailure。
关联
#5206(父单)、#5271(step 1,出处)、PR #5279(step 2)、#5189 / PR #5203(装载期兜底,门函数导出)、ADR-0121 D1/D2。
越范围发现,记录于 #5271 实现期,不在该 PR 内修(#5271 的范围是注册表与 schema 绑定)。与 #5206 的两步都不重合 —— 它是两步都落地之后剩下的那条路。
事实
packages/metadata-protocol/src/protocol.ts里没有任何endpoint-publish-gate的导入(grepvalidateApiEndpointDeclarations/identityFreeEndpointGateFailure在该包内零命中)。而saveMetaItem明确支持 direct-active 写入(该文件自己的注释:「saveMetaItem(draft AND direct-active saves)」)。于是三条通路的门覆盖是这样的:
defineStack({ apis }))ObjectStackDefinitionSchema)publishPackage/publishPackageDrafts(#5189 / PR #5279)manifest.namespace)PUT /meta/api/:name,state: active第三条路唯一的守卫是装载期兜底(#5189 / PR #5203),而它按设计跑的是免身份门 —— 命名空间门与去重门恰恰是它跳过的那两道(门函数自己的 TSDoc 写明了这一点)。
后果
一条运行时直写的 active
api行,可以声明落在别的应用的 carve-out 里的路径(/api/v1/apps/OTHERAPP/...)。免身份兜底不看命名空间,所以它会被正常索引;端点步骤只要求路径在/apps/之下。ADR-0121 D1 的原话是「路由归属靠构造而非靠约定加检查」成立 —— 但这条路上没有任何一处在构造时检查归属。去重同理:同一
METHOD + 归一化 path的第二条声明,在装载期由buildEndpointIndex按字典序裁决并error点名,但没有人在写入当场拒绝。#5271 不改变这条路的现状,它只是给这条路加了一道形状门(422);servability 的判据本来就不在注册表这一层。
建议(不预判)
两个方向,取舍留给裁决:
saveMetaItem里对api跑免身份门 + 可得身份时跑命名空间门。 难点正是 MetadataManager.publishPackage 不经 E7 端点门 —— ADR-0121 D6「匿名须限流」在 Studio 发布路径上不成立 #5189 记录过的那条:直写路径没有 manifest 身份。是否可以从packageId(该请求已经带)反查包的manifest.namespace,需要确认。api行不是「发布」,要求它走 draft → publish。 与 ADR-0121「publish 拒绝」的原文最贴,但会改变一条现有通路的语义。判据不应产生第二份 —— 无论走哪条,跑的都得是
endpoint-publish-gate.ts里那个firstFailure。关联
#5206(父单)、#5271(step 1,出处)、PR #5279(step 2)、#5189 / PR #5203(装载期兜底,门函数导出)、ADR-0121 D1/D2。