Skip to content

saveMetaItem 的 direct-active 写入是 api 的第三条门外通路 —— 命名空间门(ADR-0121 D1/D2)与去重门在这条路上无人跑 #5311

Closed

Description

@os-zhuang

越范围发现,记录于 #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。

Activity

  1. os-zhuang commented on Aug 5, 2026

    @os-zhuang
    ContributorAuthor

    分诊结论

    去重/前提复核:未发现重复单或在飞 PR。#5312(spec 车道,已合并)只把 api 补进类型注册表 + schema 绑定,其 PR 正文原样点名本单为「越范围发现,未在该 PR 内修」。#5279(engine 半边,已合并)给 publishPackageDrafts 接上了 validateApiEndpointDeclarations 全量门(含命名空间门 D1/D2),但那是 draft → publish 提升路径,不是本单描述的 saveMetaItem direct-active 写入路径。另注意到 #5488(今天由另一位同事分诊,needs-user-decision + domain:engine)是同一断链的另一方向——匹配器读不到运行时写入的 sys_metadata 行,与本单「写入时无人查命名空间/去重」是两个独立缺口,不构成重复,已相互交叉引用。

    前提复核(origin/main,packages/metadata-protocol/src/protocol.ts):确认问题依然成立。文件顶部已 import validateApiEndpointDeclarations(第 52 行),但其唯一调用点在私有方法 gateApiDraftsForPublish(约第 8163–8252 行),而该方法只被 publishPackageDrafts 调用。saveMetaItem 方法体(约第 6839–7250 行)内对 validateApiEndpointDeclarations / gateApiDraftsForPublish 零引用 —— direct-active 写入(mode !== 'draft' 时 state: 'active',约第 6868/7178 行一带)确实完全不经过命名空间门与去重门。#5311 描述的第三条门外通路在两个后续 PR 落地后依然原样存在。

    分类:needs-user-decision。本单正文明确给出 A(在 saveMetaItem 里对可得身份跑命名空间门,需要从 packageId 反查 manifest.namespace,可行性待确认)与 B(承认 direct-active 写 api 不算「发布」,强制走 draft → publish)两个方向,并声明「取舍留给裁决」——这是会改变一条现有运行时通路语义的产品契约问题,不是可以直接排队实现的确定性缺陷。

    领域:domain:engine。落地文件为 packages/metadata-protocol/src/protocol.ts,归入领域表 packages/metadata* 一档。非 driver-memory/driver-mongodb 家族,不适用 #5499 冻结裁决。

    跨领域:未见需要拆分的迹象,判据函数(endpoint-publish-gate.ts 的 firstFailure)与落地包(metadata-protocol)单一,不涉及第二个所有者。

    「本评论来自分诊座位 Routine(#5474 试点),不构成认领」。


    Generated by Claude Code

  2. claude commented on Aug 6, 2026

    @claude
    Contributor

    处置(2026-08-06):改标 pm:on-hold。

    理由:本单答案被 #5488 完全决定,该单已裁方案 2(allowRuntimeCreate: false,退役 api 运行时创建)。

    重启条件:#5488 落地后直接关闭本单——第三条门外通路随退役消失,#5086 机制在写入口当场拒绝,零工作量。仅当 #5488 实施中改道「收敛取数源」时,本单按方案 B(直写 active 强制 draft→publish,复用 #5279 全量门)入队。

    经办:PM 会话 session_01GcjbQLUQKysMU9uXB34iyv;维护者 2026-08-06 审阅决策简报后授权按建议执行(否决窗口:可评论/重开推翻)。


    Generated by Claude Code

  3. added
    pm:on-holdDecision made, deliberately deferred — no dispatch, no nag; restart condition in the hold comment
    and removed
    needs-user-decisionBlocked on a maintainer decision — do not dispatch; decision cards only, never on a PR
    on Aug 6, 2026
  4. os-zhuang commented on Aug 6, 2026

    @os-zhuang
    ContributorAuthor

    【裁决落地】维护者 2026-08-06 批复全舰队决策箱评估报告(批复「同意」),本单裁定:

    裁 B——直写 active 不算「发布」:saveMetaItem 对 api 类强制 draft→publish,发布门(ADR-0121 D1/D2 命名空间门 + 去重门)在 publish 流统一执行;判据复用 endpoint-publish-gate 的 firstFailure,⛔ 不产生第二份判据实现。

    与 #5488(裁 2:allowRuntimeCreate 改声明 false,ADR-0049 退役) 同场落地——一致世界观:api 端点只经 publish 流。唯一既有调用点(publishPackageDrafts)本就走 publish,行为不变;变更的是堵掉门外通路。

    流转:摘 needs-user-decision → pm:queue,归 engine-core 车道,与 #5488 同批派发或串行。

    评估与落地会话:session_01N3uGFF8teXbpgtbEJ1aYXu


    Generated by Claude Code

  5. added
    pm:queueReady for the PM dispatch loop
    and removed
    pm:on-holdDecision made, deliberately deferred — no dispatch, no nag; restart condition in the hold comment
    on Aug 6, 2026
  6. baozhoutao commented on Aug 7, 2026

    @baozhoutao
    Contributor

    关联决策提示:本单 10:40Z 的裁 B(强制 draft→publish)与 #5488 的裁 2(退役 allowRuntimeCreate)经实测机制互斥 —— 裁 2 之下无 draft 可 promote,裁 B 的代码不可达。两单的走向已合并为一个决策挂在 #5488 的决策箱评论(2026-08-07,含实测爆炸半径与 A/B/C 三案),维护者批复前本单不派发。若批 A,本单以 subsumed 关闭;批 B,本单单独实施。

    执行席 PM session_019Q7oc7ASjh8yxyS3Yz78We。


    Generated by Claude Code

  7. hotlong commented on Aug 7, 2026

    @hotlong
    Contributor

    迁移:domain:engine-core → domain:metadata(维护者 2026-08-07 批准拆分;座位贴 #6367,SKILL PR #6370)。落点证据:saveMetaItem direct-active 写入绕过命名空间门(ADR-0121 D1/D2)与去重门 —— metadata-protocol/src/protocol.ts 的 PUT /meta 写路径。全量分类审计 49 卡三判,本卡 MOVE/high。分诊会话:session_01BickTBKm2JYSNnrtPT8ysa。


    Generated by Claude Code

  8. added
    pm:blockedBlocked by another issue/PR — body carries Blocked-by: #N
    and removed
    pm:queueReady for the PM dispatch loop
    on Aug 8, 2026
  9. baozhoutao commented on Aug 8, 2026

    @baozhoutao
    Contributor

    State update: pm:queue → pm:blocked, Blocked-by: #5488 (domain:metadata seat, session session_01KDU3qAuJyajAQm3GkUXdfA).

    The maintainer ruling on #5488 landed 2026-08-07 16:59Z: flip api's allowRuntimeCreate to false and make the write inlet reject loudly (ADR-0049 remove side). Per this card's 2026-08-07 13:56Z linkage note, that ruling determines this card's fate: under allowRuntimeCreate: false there is no draft to promote, so the 裁 B implementation here is unreachable — this card closes as subsumed when #5488's implementation lands. Not dispatchable meanwhile; the unlock sweep on #5488's closure should close this card rather than return it to the queue.

    Note: the Blocked-by: line is recorded in this comment rather than edited into the body — the body contains escaped code spans this tool cannot round-trip byte-exact, and a corrupted body is worse than a displaced machine line. The seat sticker (#6367) carries this card in its blocked-scan set.


    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

    pm:blockedBlocked by another issue/PR — body carries Blocked-by: #N

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions