Skip to content

docs(core): narrow ANONYMOUS_DENY_BODY to the REST seam + pin both declared 401 envelopes (#5632) - #5801

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-5632-anonymous-deny-docstring-conformance
Aug 6, 2026
Merged

docs(core): narrow ANONYMOUS_DENY_BODY to the REST seam + pin both declared 401 envelopes (#5632)#5801
os-zhuang merged 1 commit into
mainfrom
claude/issue-5632-anonymous-deny-docstring-conformance

Conversation

@os-zhuang

@os-zhuang os-zhuang commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Fixes #5632

按分诊预裁范围(评论 5197848801 = 正文方案 1 + 3)执行。方案 2(wire 收敛)未做:rest-server.ts 与 runtime domains/*.ts 行为一行未动,两个 live 信封的收敛归 envelope-convergence 线(#3843 family)统一排期。

最终 diff 仅注释 + 测试,无运行时可见改动 → 请 PM 打 skip-changeset 标签(#4898 路线)。packages/qa/dogfoodprivate: true,不发版。

前提复核(先证后改,rule 6)

在动手时刻的 origin/main(f205c32)上重核:前提成立

  • ANONYMOUS_DENY_BODY 的唯一消费者是 packages/rest/src/rest-server.ts:1961(res.status(ANONYMOUS_DENY_STATUS).json(ANONYMOUS_DENY_BODY)),全仓 grep 除测试与 security/index.ts 的再导出外零命中。
  • 五个 dispatcher domain 各自调 deps.error(ANONYMOUS_DENY_MESSAGE, ANONYMOUS_DENY_STATUS, { code: ANONYMOUS_DENY_CODE }):domains/ai.ts:131domains/meta.ts:62domains/security.ts:95domains/actions.ts:132domains/automation.ts:154
  • 因此「The single 401 body shape every seam returns」确为假。

两形状归属表

生产者 匿名 401 body
GET /meta @objectstack/rest enforceAuth ANONYMOUS_DENY_BODY 原样:{ error: 'UNAUTHENTICATED', message } 扁平
GET /data/... 同上 同上 扁平
POST /actions/:object/:action/:id runtime domains/actions.ts { success: false, error: { code, message, httpStatus } } wrapper
POST /automation/:name/trigger runtime domains/automation.ts 同上 wrapper
GET /automation 同上 同上 wrapper
DELETE /automation/:name 同上 同上 wrapper

status / code / message 三者全平台一致,分歧只在 wrapper;两者均为 live(ADR-0112 2026-07-30 修正案 / #4007 记录在案)。

1. 说谎的 docstring(packages/core/src/security/anonymous-deny.ts,仅注释)

ANONYMOUS_DENY_BODY 的一行注释扩成一段,窄化为 REST seam 的形状,并写全另一半:五个 dispatcher domain 走 deps.error(...) 的 wrapper 信封;两个信封均 live 且收敛归 #3843 family;明确劝阻 body.error?.code ?? body.error 这类跨族容忍读(#5632 立单理由之一,#5569 的集成用例里就出现过这条 ?? 链);末尾指向本 PR 的 conformance 用例文件。导出值一字未改。

写法上刻意让下一个读者(尤其 AI)拿不到「唯一形状」这个读法:第一句就是 "NOT the platform's only one",并给出「你调哪个面就读哪个面 DECLARES 的信封」的操作性指令。

2. conformance 用例(packages/qa/dogfood/test/showcase-anonymous-deny-surfaces.dogfood.test.ts,+7 例,11 → 18)

落在 PR #5631 的匿名面切片同一文件,沿用其既有 harness(getSharedShowcase(),shared-showcase project),未新建基建、未改 vitest 配置、未增加 boot 开销

#5631 切片的分工(测试注释里也写了这一段):#5631 那条用例把两族各按自身声明显式断言、并单独钉住共享的 code/message —— 用的是字符串字面量。它按构造无法失败的两件事:

  1. 一个 两族都不满足却仍能通过的 body:dispatcher 那半用 toMatchObject,忽略未知键,重新嵌套 / 混合的第三种方言只要保留被匹配的子集就能过;
  2. 文件里的字面量与 @objectstack/core 实际导出的常量之间的漂移 —— 字面量只会静静变陈旧。

本 PR 补的正是这两处,不重复已覆盖的部分:

  • DENY_ENVELOPES 把两个信封声明成互斥谓词的闭世界(一族 error 是字符串且无 success,另一族 success === falseerror 是带 code/message/httpStatus 的对象且顶层无 message);declaredFamiliesOf(body) 返回它满足的所有已声明族,契约是恰好一个
  • DENIED_SEAMS 是 seam → owner → 族 的归属映射;it.each 逐 seam 断言 declaredFamiliesOf(body) 恰等于 [该 seam 声明的族]。第三种方言 → [] 红;某 seam 换族 → 另一个族名 红,且两种失败在报错里可区分。
  • code/message 用该族自己的 reader 读出后,直接与导出的 ANONYMOUS_DENY_CODE / ANONYMOUS_DENY_MESSAGE 比较(不再是本文件里的字面量)。⛔ 全程无 ?? 跨族容忍读。
  • 追加一条常量归属用例:REST 面 body toEqual(ANONYMOUS_DENY_BODY)(窄化后 docstring 的正面主张,落到线上),且 dispatcher 面 not.toEqual(ANONYMOUS_DENY_BODY)(反面那半)。将来 Envelope drift is not just service-storage: four more route modules emit bare bodies, two of them the pre-#3675 { error: '<string>' } #3843 家族真收敛、dispatcher 改发扁平信封时,红的就是这条 —— 它同时是「该回去重写那段注释」的提醒。

覆盖面据实申报(实测,非假设):五个带匿名门的 dispatcher domain 里,本 boot 只有 actions / automation 可驱动。同一 shared showcase 上探针实测:GET /ai/status 答 501 NOT_IMPLEMENTED(开源框架不含 @objectstack/service-ai,且 ai domain 的门位于路由匹配之后,本 boot 根本不执行);GET /security/permissions 答 404(showcase 注册路径不挂 /security);/meta 在本 stack 由 @objectstack/rest 提供,走的是扁平族而非 dispatcher 的 meta domain。所以 wrapper 族由 actions + automation 代表 —— 这段话写进了测试注释,哪天某个 domain 变得可达,加一行表项即可。

反向验证(方向先判后跑)

预判先写(仅测试内 fixture 层面,产品代码零改动):

变异 预判
M1 GET /automation 行喂第三种方言 { error: { code, message } }(无 success/httpStatus) 该行红,declaredFamiliesOf[]
M2 POST /actions/... 行喂扁平信封(换族) 该行红,得 [ 'rest-flat' ],报错与 M1 可区分
M3 常量归属用例里 dispatcher body 换成 ANONYMOUS_DENY_BODY 红在 .not.toEqual(ANONYMOUS_DENY_BODY)
其余 4 条表项 + #5631 与更早的 11 条 照常绿
合计 15 passed / 3 failed

实跑,逐条命中:

× anonymous 'POST /actions/:object/:action/:id' denies in exactly one declared envelope — 'dispatcher-wrapper', the one 'runtime domains/actions.ts' writes (#5632)
  AssertionError: POST /actions/:object/:action/:id: body must satisfy the dispatcher-wrapper envelope and no other
    — got {"error":"UNAUTHENTICATED","message":"..."}: expected [ 'rest-flat' ] to deeply equal [ 'dispatcher-wrapper' ]
× anonymous 'GET /automation' denies in exactly one declared envelope — 'dispatcher-wrapper', ...
  AssertionError: GET /automation: body must satisfy the dispatcher-wrapper envelope and no other
    — got {"error":{"code":"UNAUTHENTICATED","message":"..."}}: expected [] to deeply equal [ 'dispatcher-wrapper' ]
× `ANONYMOUS_DENY_BODY` is the REST seam body, and NOT the dispatcher one (#5632)
  AssertionError: a dispatcher seam must NOT be writing the REST constant

 Tests  3 failed | 15 passed (18)

变异已用 git checkout 还原,树与提交 08111f5 一致。

实跑记录(还原后)

pnpm --filter @objectstack/dogfood typecheck            -> tsc --noEmit,无输出,exit 0
pnpm --filter @objectstack/core build                   -> exit 0(core 无 typecheck 脚本,tsup 即编译门)
packages/core   npx vitest run src/security             -> Test Files 11 passed (11) | Tests 120 passed (120)
packages/qa/dogfood npx vitest run test/showcase-anonymous-deny-surfaces.dogfood.test.ts
                                                        -> Test Files 1 passed (1) | Tests 18 passed (18)   (main 基线 11,+7 = 本 PR)
node scripts/check-nul-bytes.mjs                        -> OK (scanned 5654 tracked text file(s); no raw ASCII control bytes)
node scripts/check-route-envelope.mjs                   -> ✓ 8 route module(s) audited;✓ Dispatcher domains 16 audited(0 ratcheted)
npx eslint --no-inline-config (两个改动文件)             -> exit 0,无输出
grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f]' (两个改动文件)  -> clean

自限

顺带发现(未在本 PR 修)

packages/runtime/src/endpoint-policy.ts:273anonymousDenial() 注释写着 "same code, same message, same envelope",而它调用的 apiErrorResponse(error-envelope.ts:129)产出的正是 wrapper 信封 —— 与 REST seam 并非同一个信封,是 #5632 同一句谎话的第二个落点,而 #5632 正文未枚举到。已按 Prime Directive #10 单独立单 #5800(unassigned,查重后无重复),不在本 PR 修:分诊预裁把文件面锁死在上述两个文件,改第三个文件越界。


🤖 Generated with Claude Code

https://claude.ai/code/session_01V7WetGmnfoXNn8cLieKKmx

…velopes (#5632)

`ANONYMOUS_DENY_BODY` 的注释自称 "The single 401 body shape every seam
returns",但只有 `@objectstack/rest` 的 `enforceAuth` 消费它;dispatcher 侧
五个 domain(ai / meta / security / actions / automation)走
`deps.error(...)`,发的是 wrapper 信封。注释窄化为 REST seam 的形状,并写明
另一半的归属;两个信封均为 live(ADR-0112 2026-07-30 修正案),收敛归
envelope-convergence 线(#3843 family),本单不动 wire。

conformance:在 PR #5631 落的匿名面切片同文件里补「每个 seam 的 401 body
属于且仅属于两种已声明形状之一 + 归属映射」,判形状用互斥显式判别,code /
message 直接对 `@objectstack/core` 导出的常量断言,不写 `??` 跨族容忍读。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V7WetGmnfoXNn8cLieKKmx
@vercel

vercel Bot commented Aug 6, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 6, 2026 5:59am

Request Review

@github-actions github-actions Bot added the size/m label Aug 6, 2026
@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/core.

24 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/actions-as-tools.mdx (via @objectstack/core)
  • content/docs/ai/knowledge-rag.mdx (via @objectstack/core)
  • content/docs/ai/natural-language-queries.mdx (via @objectstack/core)
  • content/docs/automation/webhooks.mdx (via @objectstack/core)
  • content/docs/concepts/north-star.mdx (via packages/core)
  • content/docs/deployment/migration-from-objectql.mdx (via @objectstack/core)
  • content/docs/kernel/contracts/index.mdx (via @objectstack/core)
  • content/docs/kernel/runtime-services/examples.mdx (via @objectstack/core)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/core)
  • content/docs/kernel/services.mdx (via @objectstack/core)
  • content/docs/permissions/authentication.mdx (via @objectstack/core)
  • content/docs/permissions/authorization.mdx (via packages/core)
  • content/docs/plugins/anatomy.mdx (via @objectstack/core)
  • content/docs/plugins/development.mdx (via @objectstack/core)
  • content/docs/plugins/index.mdx (via @objectstack/core)
  • content/docs/plugins/packages.mdx (via @objectstack/core)
  • content/docs/protocol/kernel/config-resolution.mdx (via @objectstack/core)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/core)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/core)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/core)
  • content/docs/releases/implementation-status.mdx (via @objectstack/core)
  • content/docs/releases/v12.mdx (via @objectstack/core)
  • content/docs/releases/v15.mdx (via @objectstack/core)
  • content/docs/releases/v17.mdx (via @objectstack/core)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@github-actions github-actions Bot added the tests label Aug 6, 2026
@os-zhuang os-zhuang added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 6, 2026 — with Claude
@os-zhuang
os-zhuang marked this pull request as ready for review August 6, 2026 07:09
@os-zhuang
os-zhuang added this pull request to the merge queue Aug 6, 2026
Merged via the queue into main with commit f886f29 Aug 6, 2026
28 of 29 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-5632-anonymous-deny-docstring-conformance branch August 6, 2026 07:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

2 participants