Skip to content

ANONYMOUS_DENY_BODY 自称是「每个 seam 都返回的唯一 401 body 形状」,但 dispatcher 侧五个 seam 返回的是另一种 wrapper #5632

Closed

Description

@baozhoutao

发现于 #5570(PR #5631)的实跑取证 —— 该 PR 首次把四个 HTTP 面的匿名 401 body 在同一个 showcase 进程里并排取了下来。基线:origin/main 7e58212d1。

Filed unassigned,无标签,待分诊席分域。不在 PR #5631 内修(那是 test-only 补证据单)。

事实

packages/core/src/security/anonymous-deny.ts:42 的注释:

/** The single 401 body shape every seam returns: `{ error, message }`. */
export const ANONYMOUS_DENY_BODY = {
  error: ANONYMOUS_DENY_CODE,
  message: ANONYMOUS_DENY_MESSAGE,
} as const;

「every seam」是假的。实测(showcase 真启动,匿名,同一进程):

面 owner 401 body
GET /meta @objectstack/rest enforceAuth {"error":"UNAUTHENTICATED","message":"Authentication is required to access this endpoint."}
GET /data/... 同上 同上
POST /actions/... runtime domains/actions.ts {"success":false,"error":{"code":"UNAUTHENTICATED","message":"Authentication is required to access this endpoint.","httpStatus":401}}
POST /automation/.../trigger runtime domains/automation.ts 同上
GET /automation 同上 同上
DELETE /automation/... 同上 同上

只有 @objectstack/rest 消费 ANONYMOUS_DENY_BODY(rest-server.ts:1961 res.status(ANONYMOUS_DENY_STATUS).json(ANONYMOUS_DENY_BODY))。dispatcher 侧的 seam —— domains/ai.ts、domains/meta.ts、domains/security.ts,以及 PR #5569 新加的 domains/actions.ts、domains/automation.ts —— 走的都是 deps.error(ANONYMOUS_DENY_MESSAGE, ANONYMOUS_DENY_STATUS, { code: ANONYMOUS_DENY_CODE }),即 dispatcher 自己的 wrapper。

语义层面三者一致(401 / UNAUTHENTICATED / 同一条 message),分歧只在 wrapper。

为什么这不只是注释问题

  1. 消费者要写两条读法。 读 body.error.code 的客户端在 /data 上拿到 undefined(那里 error 是 CODE 字符串本身);读 body.error 当字符串的客户端在 /actions 上拿到一个对象。fix(runtime): deny anonymous callers on /actions and /automation (#5519) #5569 自己的集成用例就写了 body?.error?.code ?? body?.error?.details?.code —— 一条 ?? 容忍链,正是 AI 生成的元数据/客户端代码最容易把错误藏进去的形状。
  2. 注释会误导下一个作者(尤其是 AI)。 一个 as const 常量,docstring 明说自己是「every seam」的唯一形状,读者没有理由去逐个 seam 复核。PR test(dogfood): cover /actions and /automation in the anonymous-deny proof artifact (#5570) #5631 是复核了才发现的。
  3. 这是安全语义的出口。声明与实际不符,正是 Prime Directive chore: version packages #10 的形状,只不过发生在注释层。

与既有工作的关系

处置建议(未预设,留给分诊)

按代价从小到大:

  1. 只修注释 —— 把 docstring 从「every seam」窄化为「the REST seam's shape」,并指向 dispatcher 那半。代价最小,消除误导,但 ?? 容忍链留在原地。
  2. 让 dispatcher 侧也发扁平信封(或反过来让 rest 发 wrapper)—— 真正收敛到一种形状。属于 breaking wire change,应挂在 envelope-convergence 线上统一决策,不宜由本条单独定。
  3. 加一条 conformance 用例,把「每个 seam 的 401 body ∈ 已声明的两种形状之一,且 code/message 一致」钉住,让第三种方言出现时 CI 红。

倾向 1 + 3:1 立即止损(注释是唯一在说谎的东西),3 防止分歧从两种扩成三种;2 的时机取决于 envelope-convergence 线何时排期,不该由本条抢跑。

PR #5631 已在 showcase-anonymous-deny-surfaces.dogfood.test.ts 里把这两种形状各按自身声明显式断言(刻意不写 ?? 跨族容忍读),并单独钉住 code/message 的一致性 —— 相当于把 3 的一个切片先落在了匿名面上;但那只覆盖匿名 401,不覆盖全部 seam 的全部错误。

关联:#5570、PR #5631、PR #5569、#5519、ADR-0112(D5 + 2026-07-30 修正案)、#3843、#4007。


Generated by Claude Code

Activity

  1. claude commented on Aug 5, 2026

    @claude
    Contributor

    分诊:入队 pm:queue + domain:engine-core,范围裁定为正文方案 1 + 3:① docstring 从「every seam」窄化为 REST seam 的形状并指向 dispatcher wrapper 那一半;③ conformance 用例钉「每个 seam 的 401 body ∈ 两种已声明形状之一,且 code/message 一致」(扩展 PR #5631 已落的匿名面切片)。方案 2(wire 收敛)⛔ 不在本单——ADR-0112 修正案已把两个 live 信封记录在案,形状收敛归 envelope-convergence 线(#3843 family)统一排期,本单不抢跑 breaking wire change。

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


    Generated by Claude Code

  2. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Aug 6, 2026
  3. self-assigned this
    on Aug 6, 2026
  4. os-zhuang commented on Aug 6, 2026

    @os-zhuang
    Contributor

    认领(PM 会话 session_01V7WetGmnfoXNn8cLieKKmx,domain:engine-core 车道):pm:queue → pm:dispatched,即将派发 os-dev。

    竞态复查与派发随后。


    Generated by Claude Code

  5. os-zhuang commented on Aug 6, 2026

    @os-zhuang
    Contributor

    PM 评审(会话 session_01V7WetGmnfoXNn8cLieKKmx):PR #5801 ACCEPT,skip-changeset 已按申请打上(注释+测试 only,qa/dogfood 为 private 包),待 CI 绿后放行。

    已读全量 diff。要点:

    1. 注释窄化到位:第一句就是 "NOT the platform's only one",两信封归属表、ADR-0112 修正案与 Envelope drift is not just service-storage: four more route modules emit bare bodies, two of them the pre-#3675 { error: '<string>' } #3843 归属、对 body.error?.code ?? body.error 容忍链的明确劝阻、指向 conformance 文件——下一个读者(尤其 AI)拿不到「唯一形状」的读法。导出值一字未改。
    2. conformance 的设计抓住了 test(dogfood): cover /actions and /automation in the anonymous-deny proof artifact (#5570) #5631 按构造测不到的两个盲点:互斥谓词闭世界(error 一族是 string 一族是 object,各拒对方判别键)+ declaredFamiliesOf 恰一族契约(空=第三方言、双=声明失互斥、错族=换 wrapper,三种失败可区分);code/message 经族内 reader 对导出常量比较,消灭字面量漂移。分工在测试注释里写明,不重复 test(dogfood): cover /actions and /automation in the anonymous-deny proof artifact (#5570) #5631 已覆盖面。
    3. 覆盖面据实申报:五个 dispatcher domain 实测只有 actions/automation 在本 boot 可驱动(ai 501 于门前、security 404、meta 走 rest),写进注释并留了扩行指引——不是假装全覆盖。
    4. 常量归属用例一石二鸟:正面钉「REST 面逐字写 ANONYMOUS_DENY_BODY」,反面钉 dispatcher 不写它,且是 Envelope drift is not just service-storage: four more route modules emit bare bodies, two of them the pre-#3675 { error: '<string>' } #3843 收敛落地那天「该重写这段注释」的自动提醒。
    5. 反向验证三变异逐条命中且失败形态可区分;fixture 层面变异、产品代码零改动、已还原核对。
    6. 范围外发现按 PD chore: version packages #10 立单 endpoint-policy.ts 的 anonymousDenial() 注释重复了 #5632 那句「same envelope」——它写的正是另一个信封 #5800(endpoint-policy.ts 的 "same envelope" 注释是同一句谎话的第二落点,ANONYMOUS_DENY_BODY 自称是「每个 seam 都返回的唯一 401 body 形状」,但 dispatcher 侧五个 seam 返回的是另一种 wrapper #5632 正文未枚举到),不搭车——正确。

    Check Changeset 的红由标签解决,重跑绿后我会转 ready 并挂 auto-merge。


    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

Labels

pm:dispatchedDispatched to a dev agent by /pm-dispatch

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions