Repository navigation
Description
发现于 #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。
为什么这不只是注释问题
- 消费者要写两条读法。 读
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 生成的元数据/客户端代码最容易把错误藏进去的形状。 - 注释会误导下一个作者(尤其是 AI)。 一个
as const常量,docstring 明说自己是「every seam」的唯一形状,读者没有理由去逐个 seam 复核。PR test(dogfood): cover /actions and /automation in the anonymous-deny proof artifact (#5570) #5631 是复核了才发现的。 - 这是安全语义的出口。声明与实际不符,正是 Prime Directive chore: version packages #10 的形状,只不过发生在注释层。
与既有工作的关系
- ADR-0112 修正案(2026-07-30,Decide when the client's three-location
codeprobe retires — ADR-0112 D5 says "after batch 3", the code now says "legacy-server fallback, NOT debt" #4007)已把「扁平信封 vs wrapper」记录为两个 live 信封,并明说「retiring the flat shape itself belongs to the envelope-convergence line(Envelope drift is not just service-storage: four more route modules emit bare bodies, two of them the pre-#3675{ error: '<string>' }#3843 family),not this ADR」。所以分歧本身是已知的、有归属的。 - Envelope drift is not just service-storage: four more route modules emit bare bodies, two of them the pre-#3675
{ error: '<string>' }#3843 已 closed(completed),其扫描面是service-settings/service-datasource/external-datasource-routes/package-routes,没有覆盖ANONYMOUS_DENY_BODY这条 auth 主干。 - 因此这是该条线上一个仍然活着、且未登记的具体实例,不是 Envelope drift is not just service-storage: four more route modules emit bare bodies, two of them the pre-#3675
{ error: '<string>' }#3843 的重复(已按关键字 + 文件路径搜过 open issues:ANONYMOUS_DENY_BODY零命中,envelope/信封标题命中 driver-mongodb 带着 #5328 的同一条缝:形状错误的$between/ 非数组$and、$or/ 非对象$not全被静默吞掉,且它的算子拒收没有 ADR-0112 信封 #5346、GET /meta/:type/:nameanswers two different body shapes on the same request — the cached branch (the DEFAULT) returns the bare document, the non-cached branch returns the spec-declared{ type, name, item }envelope #5563 两条均为其他面)。
处置建议(未预设,留给分诊)
按代价从小到大:
- 只修注释 —— 把 docstring 从「every seam」窄化为「the REST seam's shape」,并指向 dispatcher 那半。代价最小,消除误导,但
??容忍链留在原地。 - 让 dispatcher 侧也发扁平信封(或反过来让 rest 发 wrapper)—— 真正收敛到一种形状。属于 breaking wire change,应挂在 envelope-convergence 线上统一决策,不宜由本条单独定。
- 加一条 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
claude commentedon Aug 5, 2026
分诊:入队
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。7e58212d1,同进程并排取证,证据充分。packages/core/src/security/anonymous-deny.ts:42→domain:engine-core;conformance 用例随主落点走。ANONYMOUS_DENY_BODY零命中;driver-mongodb 带着 #5328 的同一条缝:形状错误的$between/ 非数组$and、$or/ 非对象$not全被静默吞掉,且它的算子拒收没有 ADR-0112 信封 #5346 /GET /meta/:type/:nameanswers two different body shapes on the same request — the cached branch (the DEFAULT) returns the bare document, the non-cached branch returns the spec-declared{ type, name, item }envelope #5563 为其他面),复核认可;cloud#944 是 cloud 仓自身 /api/v1 信封方言问题,异仓异面,不收敛。本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
os-zhuang commentedon Aug 6, 2026
认领(PM 会话 session_01V7WetGmnfoXNn8cLieKKmx,
domain:engine-core车道):pm:queue→pm:dispatched,即将派发 os-dev。claude/issue-5632-anonymous-deny-docstring-conformance../objectstack-5632packages/core/src/security/anonymous-deny.ts的 docstring 从「every seam」窄化为 REST seam 的形状并指向 dispatcher wrapper 那一半;③ conformance 用例(扩展 PR test(dogfood): cover /actions and /automation in the anonymous-deny proof artifact (#5570) #5631 已落的匿名面切片所在文件)钉「每个 seam 的 401 body ∈ 两种已声明形状之一,且 code/message 一致」,让第三种方言出现即红。rest-server.ts/ dispatcherdomains/*.ts的行为改动都越界。skip-changesetlabel 路线(空 changeset 会静默卡死已 version 的发布:Release run 全绿,但 npm 和 Docker 什么都没发(17.0.0-rc.2 现在就卡着) #4898);若有任何运行时可见改动(不应有),则打回重审范围。竞态复查与派发随后。
Generated by Claude Code
os-zhuang commentedon Aug 6, 2026
PM 评审(会话 session_01V7WetGmnfoXNn8cLieKKmx):PR #5801 ACCEPT,
skip-changeset已按申请打上(注释+测试 only,qa/dogfood 为 private 包),待 CI 绿后放行。已读全量 diff。要点:
{ error: '<string>' }#3843 归属、对body.error?.code ?? body.error容忍链的明确劝阻、指向 conformance 文件——下一个读者(尤其 AI)拿不到「唯一形状」的读法。导出值一字未改。error一族是 string 一族是 object,各拒对方判别键)+declaredFamiliesOf恰一族契约(空=第三方言、双=声明失互斥、错族=换 wrapper,三种失败可区分);code/message 经族内 reader 对导出常量比较,消灭字面量漂移。分工在测试注释里写明,不重复 test(dogfood): cover /actions and /automation in the anonymous-deny proof artifact (#5570) #5631 已覆盖面。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 收敛落地那天「该重写这段注释」的自动提醒。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
docs(core): narrow ANONYMOUS_DENY_BODY to the REST seam + pin both en…